feat: support defining data in constructor/initialzers
This commit is contained in:
		
							parent
							
								
									d9e3ad72c0
								
							
						
					
					
						commit
						60e803ce62
					
				@ -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> {
 | 
			
		||||
 | 
			
		||||
@ -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'
 | 
			
		||||
 | 
			
		||||
@ -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 || {})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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() {
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user