]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: 2.x options support
authorEvan You <yyx990803@gmail.com>
Wed, 4 Sep 2019 02:25:38 +0000 (22:25 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 4 Sep 2019 02:25:38 +0000 (22:25 -0400)
jest.config.js
packages/reactivity/src/computed.ts
packages/runtime-core/src/apiLifecycle.ts
packages/runtime-core/src/apiOptions.ts [new file with mode: 0644]
packages/runtime-core/src/apiReactivity.ts
packages/runtime-core/src/component.ts

index 08d09f94fed7c82c7c7dc82f0598b1d650cf3165..ab41aafa3781d5c89d44a039e2bc1ad1f805898a 100644 (file)
@@ -2,8 +2,9 @@ module.exports = {
   preset: 'ts-jest',
   globals: {
     __DEV__: true,
-    __COMPAT__: false,
-    __JSDOM__: true
+    __JSDOM__: true,
+    __FEATURE_OPTIONS__: true,
+    __FEATURE_PRODUCTION_TIP__: false
   },
   coverageDirectory: 'coverage',
   coverageReporters: ['html', 'lcov', 'text'],
index 5504ac79bbc57942de7a59f18de39fa8438bd590..d795564404a85c63574a293bb198ae49cd7e44c8 100644 (file)
@@ -7,7 +7,7 @@ export interface ComputedRef<T> {
   readonly effect: ReactiveEffect
 }
 
-export interface ComputedOptions<T> {
+export interface ComputedOptions<T = any> {
   get: () => T
   set: (v: T) => void
 }
index cded357b65e85e7b20df4d48072865784f513495..60470d8280b63b76744a293c1a197267cdbb3f8d 100644 (file)
@@ -12,7 +12,7 @@ import { capitalize } from '@vue/shared'
 function injectHook(
   type: LifecycleHooks,
   hook: Function,
-  target: ComponentInstance | null = currentInstance
+  target: ComponentInstance | null
 ) {
   if (target) {
     ;(target[type] || (target[type] = [])).push((...args: any[]) => {
@@ -26,7 +26,7 @@ function injectHook(
     })
   } else if (__DEV__) {
     const apiName = `on${capitalize(
-      ErrorTypeStrings[name].replace(/ hook$/, '')
+      ErrorTypeStrings[type].replace(/ hook$/, '')
     )}`
     warn(
       `${apiName} is called when there is no active component instance to be ` +
diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts
new file mode 100644 (file)
index 0000000..2ab2af2
--- /dev/null
@@ -0,0 +1,203 @@
+import {
+  ComponentInstance,
+  Data,
+  ComponentOptions,
+  ComponentRenderProxy
+} from './component'
+import {
+  isFunction,
+  extend,
+  isString,
+  isObject,
+  isArray,
+  EMPTY_OBJ
+} from '@vue/shared'
+import { computed, ComputedOptions } from './apiReactivity'
+import { watch } from './apiWatch'
+import { provide, inject } from './apiInject'
+import {
+  onBeforeMount,
+  onMounted,
+  onBeforeUpdate,
+  onUpdated,
+  onErrorCaptured,
+  onRenderTracked,
+  onBeforeUnmount,
+  onUnmounted
+} from './apiLifecycle'
+import { DebuggerEvent } from '@vue/reactivity'
+
+type LegacyComponent =
+  | ComponentOptions
+  | {
+      new (): ComponentRenderProxy
+      options: ComponentOptions
+    }
+
+// TODO type inference for these options
+export interface LegacyOptions {
+  el?: any
+
+  // state
+  data?: Data | (() => Data)
+  computed?: Record<string, (() => any) | ComputedOptions>
+  methods?: Record<string, Function>
+  // TODO watch array
+  watch?: Record<
+    string,
+    | string
+    | Function
+    | { handler: Function; deep?: boolean; immediate: boolean }
+  >
+  provide?: Data | (() => Data)
+  inject?:
+    | string[]
+    | Record<
+        string | symbol,
+        string | symbol | { from: string | symbol; default: any }
+      >
+
+  // composition
+  mixins?: LegacyComponent[]
+  extends?: LegacyComponent
+
+  // lifecycle
+  beforeCreate?(): void
+  created?(): void
+  beforeMount?(): void
+  mounted?(): void
+  beforeUpdate?(): void
+  updated?(): void
+  activated?(): void
+  decativated?(): void
+  beforeDestroy?(): void
+  destroyed?(): void
+  renderTracked?(e: DebuggerEvent): void
+  renderTriggered?(e: DebuggerEvent): void
+  errorCaptured?(): boolean
+}
+
+export function processOptions(instance: ComponentInstance) {
+  const data =
+    instance.data === EMPTY_OBJ ? (instance.data = {}) : instance.data
+  const ctx = instance.renderProxy as any
+  const {
+    data: dataOptions,
+    computed: computedOptions,
+    methods,
+    watch: watchOptions,
+    provide: provideOptions,
+    inject: injectOptions,
+    // beforeCreate is handled separately
+    created,
+    beforeMount,
+    mounted,
+    beforeUpdate,
+    updated,
+    // TODO activated
+    // TODO decativated
+    beforeDestroy,
+    destroyed,
+    renderTracked,
+    renderTriggered,
+    errorCaptured
+  } = instance.type as ComponentOptions
+
+  if (dataOptions) {
+    extend(data, isFunction(dataOptions) ? dataOptions.call(ctx) : dataOptions)
+  }
+
+  if (computedOptions) {
+    for (const key in computedOptions) {
+      data[key] = computed(computedOptions[key] as any)
+    }
+  }
+
+  if (methods) {
+    for (const key in methods) {
+      data[key] = methods[key].bind(ctx)
+    }
+  }
+
+  if (watchOptions) {
+    for (const key in watchOptions) {
+      const raw = watchOptions[key]
+      const getter = () => ctx[key]
+      if (isString(raw)) {
+        const handler = data[key]
+        if (isFunction(handler)) {
+          watch(getter, handler.bind(ctx))
+        } else if (__DEV__) {
+          // TODO warn invalid watch handler path
+        }
+      } else if (isFunction(raw)) {
+        watch(getter, raw.bind(ctx))
+      } else if (isObject(raw)) {
+        watch(getter, raw.handler.bind(ctx), {
+          deep: !!raw.deep,
+          lazy: !raw.immediate
+        })
+      } else if (__DEV__) {
+        // TODO warn invalid watch options
+      }
+    }
+  }
+
+  if (provideOptions) {
+    const provides = isFunction(provideOptions)
+      ? provideOptions.call(ctx)
+      : provideOptions
+    for (const key in provides) {
+      provide(key, provides[key])
+    }
+  }
+
+  if (injectOptions) {
+    if (isArray(injectOptions)) {
+      for (let i = 0; i < injectOptions.length; i++) {
+        const key = injectOptions[i]
+        data[key] = inject(key)
+      }
+    } else {
+      for (const key in injectOptions) {
+        const opt = injectOptions[key]
+        if (isObject(opt)) {
+          data[key] = inject(opt.from, opt.default)
+        } else {
+          data[key] = inject(opt)
+        }
+      }
+    }
+  }
+
+  if (created) {
+    created.call(ctx)
+  }
+  if (beforeMount) {
+    onBeforeMount(beforeMount.bind(ctx))
+  }
+  if (mounted) {
+    onMounted(mounted.bind(ctx))
+  }
+  if (beforeUpdate) {
+    onBeforeUpdate(beforeUpdate.bind(ctx))
+  }
+  if (updated) {
+    onUpdated(updated.bind(ctx))
+  }
+  if (errorCaptured) {
+    onErrorCaptured(errorCaptured.bind(ctx))
+  }
+  if (renderTracked) {
+    onRenderTracked(renderTracked.bind(ctx))
+  }
+  if (renderTriggered) {
+    onRenderTracked(renderTriggered.bind(ctx))
+  }
+  if (beforeDestroy) {
+    onBeforeUnmount(beforeDestroy.bind(ctx))
+  }
+  if (destroyed) {
+    onUnmounted(destroyed.bind(ctx))
+  }
+}
index 76bcf283c95e82a7cd5b42323a8b6c56682ca081..40757ae49003ec11c02e0b6e0dc0cb77094cb6d7 100644 (file)
@@ -17,7 +17,8 @@ export {
   OperationTypes,
   Ref,
   ComputedRef,
-  UnwrapRef
+  UnwrapRef,
+  ComputedOptions
 } from '@vue/reactivity'
 
 import {
index 8d26f4f03d476ce00091d5fd8d5b8043c727f923..1b6d55bbf0e14660b78eb7fc67c10e6470791af5 100644 (file)
@@ -1,6 +1,13 @@
 import { VNode, normalizeVNode, VNodeChild, createVNode, Empty } from './vnode'
 import { ReactiveEffect, UnwrapRef, reactive, readonly } from '@vue/reactivity'
-import { EMPTY_OBJ, isFunction, capitalize, NOOP, isArray } from '@vue/shared'
+import {
+  EMPTY_OBJ,
+  isFunction,
+  capitalize,
+  NOOP,
+  isArray,
+  isObject
+} from '@vue/shared'
 import { RenderProxyHandlers } from './componentProxy'
 import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
 import { Slots } from './componentSlots'
@@ -15,6 +22,7 @@ import {
 } from './errorHandling'
 import { AppContext, createAppContext, resolveAsset } from './apiApp'
 import { Directive } from './directives'
+import { processOptions, LegacyOptions } from './apiOptions'
 
 export type Data = { [key: string]: unknown }
 
@@ -38,15 +46,16 @@ type RenderFunction<Props = {}, RawBindings = {}> = <
   this: ComponentRenderProxy<Props, Bindings>
 ) => VNodeChild
 
-interface ComponentOptionsBase<Props, RawBindings> {
+interface ComponentOptionsBase<Props, RawBindings> extends LegacyOptions {
   setup?: (
     props: Props,
     ctx: SetupContext
   ) => RawBindings | (() => VNodeChild) | void
+  name?: string
+  template?: string
   render?: RenderFunction<Props, RawBindings>
   components?: Record<string, Component>
   directives?: Record<string, Directive>
-  // TODO full 2.x options compat
 }
 
 interface ComponentOptionsWithoutProps<Props = {}, RawBindings = {}>
@@ -279,6 +288,7 @@ export const setCurrentInstance = (instance: ComponentInstance | null) => {
 }
 
 export function setupStatefulComponent(instance: ComponentInstance) {
+  currentInstance = instance
   const Component = instance.type as ComponentOptions
   // 1. create render proxy
   instance.renderProxy = new Proxy(instance, RenderProxyHandlers) as any
@@ -291,15 +301,12 @@ export function setupStatefulComponent(instance: ComponentInstance) {
   if (setup) {
     const setupContext = (instance.setupContext =
       setup.length > 1 ? createSetupContext(instance) : null)
-
-    currentInstance = instance
     const setupResult = callWithErrorHandling(
       setup,
       instance,
       ErrorTypes.SETUP_FUNCTION,
       [propsProxy, setupContext]
     )
-    currentInstance = null
 
     if (isFunction(setupResult)) {
       // setup returned an inline render function
@@ -322,15 +329,32 @@ export function setupStatefulComponent(instance: ComponentInstance) {
       }
       // setup returned bindings.
       // assuming a render function compiled from template is present.
-      instance.data = reactive(setupResult || {})
+      if (isObject(setupResult)) {
+        instance.data = setupResult
+      } else if (__DEV__ && setupResult !== undefined) {
+        warn(
+          `setup() should return an object. Received: ${
+            setupResult === null ? 'null' : typeof setupResult
+          }`
+        )
+      }
       instance.render = (Component.render || NOOP) as RenderFunction
     }
   } else {
     if (__DEV__ && !Component.render) {
-      // TODO warn missing render fn
+      warn(
+        `Component is missing render function. Either provide a template or ` +
+          `return a render function from setup().`
+      )
     }
     instance.render = Component.render as RenderFunction
   }
+  // support for 2.x options
+  if (__FEATURE_OPTIONS__) {
+    processOptions(instance)
+  }
+  instance.data = reactive(instance.data === EMPTY_OBJ ? {} : instance.data)
+  currentInstance = null
 }
 
 // used to identify a setup context proxy