]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: support defining data in constructor/initialzers
authorEvan You <yyx990803@gmail.com>
Thu, 11 Oct 2018 17:54:35 +0000 (13:54 -0400)
committerEvan You <yyx990803@gmail.com>
Thu, 11 Oct 2018 17:54:35 +0000 (13:54 -0400)
packages/core/src/component.ts
packages/core/src/componentProps.ts
packages/core/src/componentState.ts
packages/core/src/componentUtils.ts
packages/core/src/optional/asyncComponent.ts
packages/vue/src/index.ts

index ec5254d4645883f26495d52c3256ec9730638602..41997f369373b87a59cf95971e166a98dff0766e 100644 (file)
@@ -11,7 +11,7 @@ import { setupWatcher } from './componentWatch'
 import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
 import { nextTick } from '@vue/scheduler'
 import { ErrorTypes } from './errorHandling'
-import { resolveComponentOptions } from './componentUtils'
+import { initializeComponentInstance } from './componentUtils'
 
 export interface ComponentClass extends ComponentClassOptions {
   options?: ComponentOptions
@@ -101,9 +101,7 @@ class InternalComponent {
   public _inactiveRoot: boolean = false
 
   constructor() {
-    this.$options =
-      (this.constructor as ComponentClass).options ||
-      resolveComponentOptions(this.constructor as ComponentClass)
+    initializeComponentInstance(this as any)
   }
 
   $nextTick(fn: () => any): Promise<any> {
index e10d6a953353211c0fd3ea1cfbe6643070cf01d2..d8618acb749da69f62543d4ecc6b17f82492c524 100644 (file)
@@ -9,6 +9,8 @@ import {
 } from './componentOptions'
 import { EMPTY_OBJ, camelize, hyphenate, capitalize } from './utils'
 
+const EMPTY_PROPS = { props: EMPTY_OBJ }
+
 export function initializeProps(
   instance: ComponentInstance,
   options: ComponentPropsOptions | undefined,
@@ -19,45 +21,6 @@ export function initializeProps(
   instance.$attrs = immutable(attrs || {})
 }
 
-export function updateProps(instance: ComponentInstance, nextData: Data) {
-  // instance.$props and instance.$attrs are observables that should not be
-  // replaced. Instead, we mutate them to match latest props, which will trigger
-  // updates if any value that's been used in child component has changed.
-  if (nextData != null) {
-    const { props: nextProps, attrs: nextAttrs } = resolveProps(
-      nextData,
-      instance.constructor.props
-    )
-    // unlock to temporarily allow mutatiing props
-    unlock()
-    const props = instance.$props
-    const rawProps = unwrap(props)
-    for (const key in rawProps) {
-      if (!nextProps.hasOwnProperty(key)) {
-        delete (props as any)[key]
-      }
-    }
-    for (const key in nextProps) {
-      ;(props as any)[key] = nextProps[key]
-    }
-    if (nextAttrs) {
-      const attrs = instance.$attrs
-      const rawAttrs = unwrap(attrs)
-      for (const key in rawAttrs) {
-        if (!nextAttrs.hasOwnProperty(key)) {
-          delete attrs[key]
-        }
-      }
-      for (const key in nextAttrs) {
-        attrs[key] = nextAttrs[key]
-      }
-    }
-    lock()
-  }
-}
-
-const EMPTY_PROPS = { props: EMPTY_OBJ }
-
 // resolve raw VNode data.
 // - filter out reserved keys (key, ref, slots)
 // - extract class and style into $attrs (to be merged onto child
@@ -125,6 +88,43 @@ export function resolveProps(
   return { props, attrs }
 }
 
+export function updateProps(instance: ComponentInstance, nextData: Data) {
+  // instance.$props and instance.$attrs are observables that should not be
+  // replaced. Instead, we mutate them to match latest props, which will trigger
+  // updates if any value that's been used in child component has changed.
+  if (nextData != null) {
+    const { props: nextProps, attrs: nextAttrs } = resolveProps(
+      nextData,
+      instance.constructor.props
+    )
+    // unlock to temporarily allow mutatiing props
+    unlock()
+    const props = instance.$props
+    const rawProps = unwrap(props)
+    for (const key in rawProps) {
+      if (!nextProps.hasOwnProperty(key)) {
+        delete (props as any)[key]
+      }
+    }
+    for (const key in nextProps) {
+      ;(props as any)[key] = nextProps[key]
+    }
+    if (nextAttrs) {
+      const attrs = instance.$attrs
+      const rawAttrs = unwrap(attrs)
+      for (const key in rawAttrs) {
+        if (!nextAttrs.hasOwnProperty(key)) {
+          delete attrs[key]
+        }
+      }
+      for (const key in nextAttrs) {
+        attrs[key] = nextAttrs[key]
+      }
+    }
+    lock()
+  }
+}
+
 const enum BooleanFlags {
   shouldCast = '1',
   shouldCastTrue = '2'
index 114f811385cfdb878d51ce22269978ee73013117..931794f42755d2503fc1a7713fcaaad9061c5576 100644 (file)
@@ -1,12 +1,21 @@
-import { EMPTY_OBJ } from './utils'
+// import { EMPTY_OBJ } from './utils'
 import { ComponentInstance } from './component'
 import { observable } from '@vue/observer'
 
+const internalRE = /^_|^\$/
+
 export function initializeState(instance: ComponentInstance) {
   if (instance.data) {
     instance._rawData = instance.data()
-    instance.$data = observable(instance._rawData)
   } else {
-    instance.$data = EMPTY_OBJ
+    const keys = Object.keys(instance)
+    const data = (instance._rawData = {} as any)
+    for (let i = 0; i < keys.length; i++) {
+      const key = keys[i]
+      if (!internalRE.test(key)) {
+        data[key] = (instance as any)[key]
+      }
+    }
   }
+  instance.$data = observable(instance._rawData || {})
 }
index 8f9ab8b6f812c4fe9dcd4779776fb538d1e74b7d..a838f3f6735619677993fabf35fe0bd389fce14b 100644 (file)
@@ -16,28 +16,62 @@ import { ComponentOptions } from './componentOptions'
 import { createRenderProxy } from './componentProxy'
 import { handleError, ErrorTypes } from './errorHandling'
 
+let currentVNode: VNode | null = null
+let currentContextVNode: MountedVNode | null = null
+
 export function createComponentInstance(
   vnode: VNode,
   Component: ComponentClass,
   contextVNode: MountedVNode | null
 ): ComponentInstance {
+  // component instance creation is done in two steps.
+  // first, `initializeComponentInstance` is called inside base component
+  // constructor as the instance is created so that
+  currentVNode = vnode
+  currentContextVNode = contextVNode
   const instance = (vnode.children = new Component()) as ComponentInstance
-  instance.$parentVNode = vnode as MountedVNode
+  // then we finish the initialization by collecting properties set on the
+  // instance
+  initializeState(instance)
+  initializeComputed(instance, Component.computed)
+  initializeWatch(instance, Component.watch)
+  instance.$slots = currentVNode.slots || EMPTY_OBJ
+  if (instance.created) {
+    instance.created.call(instance.$proxy)
+  }
+  currentVNode = currentContextVNode = null
+  return instance
+}
+
+// this is called inside the base component's constructor
+// it initializes all the way up to props so that they are available
+// inside the extended component's constructor
+export function initializeComponentInstance(instance: ComponentInstance) {
+  if (__DEV__ && currentVNode === null) {
+    throw new Error(
+      `Component classes are not meant to be manually instantiated.`
+    )
+  }
+
+  instance.$options =
+    instance.constructor.options ||
+    resolveComponentOptions(instance.constructor)
+  instance.$parentVNode = currentVNode as MountedVNode
 
   // renderProxy
   const proxy = (instance.$proxy = createRenderProxy(instance))
 
-  // pointer management
-  if (contextVNode !== null) {
+  // parent chain management
+  if (currentContextVNode !== null) {
     // locate first non-functional parent
     while (
-      contextVNode !== null &&
-      contextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
-      contextVNode.contextVNode !== null
+      currentContextVNode !== null &&
+      currentContextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
+      currentContextVNode.contextVNode !== null
     ) {
-      contextVNode = contextVNode.contextVNode as any
+      currentContextVNode = currentContextVNode.contextVNode as any
     }
-    const parentComponent = (contextVNode as VNode)
+    const parentComponent = (currentContextVNode as VNode)
       .children as ComponentInstance
     instance.$parent = parentComponent.$proxy
     instance.$root = parentComponent.$root
@@ -46,20 +80,15 @@ export function createComponentInstance(
     instance.$root = proxy
   }
 
-  // lifecycle
+  // beforeCreate hook is called right in the constructor
   if (instance.beforeCreate) {
     instance.beforeCreate.call(proxy)
   }
-  initializeProps(instance, Component.props, vnode.data)
-  initializeState(instance)
-  initializeComputed(instance, Component.computed)
-  initializeWatch(instance, Component.watch)
-  instance.$slots = vnode.slots || EMPTY_OBJ
-  if (instance.created) {
-    instance.created.call(proxy)
-  }
-
-  return instance as ComponentInstance
+  initializeProps(
+    instance,
+    instance.constructor.props,
+    (currentVNode as VNode).data
+  )
 }
 
 export function renderInstanceRoot(instance: ComponentInstance): VNode {
@@ -218,11 +247,13 @@ export function createComponentClassFromOptions(
 export function resolveComponentOptions(
   Component: ComponentClass
 ): ComponentOptions {
-  const keys = Object.keys(Component)
+  const descriptors = Object.getOwnPropertyDescriptors(Component)
   const options = {} as any
-  for (let i = 0; i < keys.length; i++) {
-    const key = keys[i]
-    options[key] = (Component as any)[key]
+  for (const key in descriptors) {
+    const descriptor = descriptors[key]
+    if (descriptor.enumerable || descriptor.get) {
+      options[key] = descriptor.get ? descriptor.get() : descriptor.value
+    }
   }
   Component.computed = options.computed = resolveComputedOptions(Component)
   Component.options = options
index 5b5a2c70c52e81f78c67ae889a132d0fa59d8231..e0a6d0d4300340898cfd6a10e72dd383e1432d12 100644 (file)
@@ -18,13 +18,6 @@ interface AsyncComponentFullOptions {
 
 type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
 
-interface AsyncContainerData {
-  comp: ComponentType | null
-  err: Error | null
-  delayed: boolean
-  timedOut: boolean
-}
-
 export function createAsyncComponent(
   options: AsyncComponentOptions
 ): ComponentClass {
@@ -40,15 +33,11 @@ export function createAsyncComponent(
     error: errorComp
   } = options
 
-  return class AsyncContainer extends Component<{}, AsyncContainerData> {
-    data() {
-      return {
-        comp: null,
-        err: null,
-        delayed: false,
-        timedOut: false
-      }
-    }
+  return class AsyncContainer extends Component {
+    comp: ComponentType | null = null
+    err: Error | null = null
+    delayed: boolean = false
+    timedOut: boolean = false
 
     // doing this in beforeMount so this is non-SSR only
     beforeMount() {
index 7fed1d2fa7b4c714c5bbc7176e46c5c781152244..7bcae758689ba8adb4e1a15940ba690968c7c174 100644 (file)
@@ -2,33 +2,21 @@ import {
   h,
   render,
   nextTick,
-  Component,
   createComponentInstance,
   createComponentClassFromOptions
 } from '@vue/renderer-dom'
 
-// Note: typing for this is intentionally loose, as it will be using 2.x types.
-class Vue extends Component {
+class Vue {
   static h = h
   static render = render
   static nextTick = nextTick
 
+  // Note: typing for this is intentionally loose, as it will be using 2.x types.
   constructor(options: any) {
-    super()
-    if (!options) {
-      return
-    }
-
-    // in compat mode, h() can take an options object and will convert it
-    // to a 3.x class-based component.
-    const Component = createComponentClassFromOptions(options)
+    // convert it to a class
+    const Component = createComponentClassFromOptions(options || {})
     const vnode = h(Component)
-    // the component class is cached on the options object as ._normalized
     const instance = createComponentInstance(vnode, Component, null)
-    // set the instance on the vnode before mounting.
-    // the mount function will skip creating a new instance if it finds an
-    // existing one.
-    vnode.children = instance
 
     function mount(el: any) {
       const dom = typeof el === 'string' ? document.querySelector(el) : el