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 { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
|
||||||
import { nextTick } from '@vue/scheduler'
|
import { nextTick } from '@vue/scheduler'
|
||||||
import { ErrorTypes } from './errorHandling'
|
import { ErrorTypes } from './errorHandling'
|
||||||
import { resolveComponentOptions } from './componentUtils'
|
import { initializeComponentInstance } from './componentUtils'
|
||||||
|
|
||||||
export interface ComponentClass extends ComponentClassOptions {
|
export interface ComponentClass extends ComponentClassOptions {
|
||||||
options?: ComponentOptions
|
options?: ComponentOptions
|
||||||
@ -101,9 +101,7 @@ class InternalComponent {
|
|||||||
public _inactiveRoot: boolean = false
|
public _inactiveRoot: boolean = false
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.$options =
|
initializeComponentInstance(this as any)
|
||||||
(this.constructor as ComponentClass).options ||
|
|
||||||
resolveComponentOptions(this.constructor as ComponentClass)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$nextTick(fn: () => any): Promise<any> {
|
$nextTick(fn: () => any): Promise<any> {
|
||||||
|
@ -9,6 +9,8 @@ import {
|
|||||||
} from './componentOptions'
|
} from './componentOptions'
|
||||||
import { EMPTY_OBJ, camelize, hyphenate, capitalize } from './utils'
|
import { EMPTY_OBJ, camelize, hyphenate, capitalize } from './utils'
|
||||||
|
|
||||||
|
const EMPTY_PROPS = { props: EMPTY_OBJ }
|
||||||
|
|
||||||
export function initializeProps(
|
export function initializeProps(
|
||||||
instance: ComponentInstance,
|
instance: ComponentInstance,
|
||||||
options: ComponentPropsOptions | undefined,
|
options: ComponentPropsOptions | undefined,
|
||||||
@ -19,45 +21,6 @@ export function initializeProps(
|
|||||||
instance.$attrs = immutable(attrs || {})
|
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.
|
// resolve raw VNode data.
|
||||||
// - filter out reserved keys (key, ref, slots)
|
// - filter out reserved keys (key, ref, slots)
|
||||||
// - extract class and style into $attrs (to be merged onto child
|
// - extract class and style into $attrs (to be merged onto child
|
||||||
@ -125,6 +88,43 @@ export function resolveProps(
|
|||||||
return { props, attrs }
|
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 {
|
const enum BooleanFlags {
|
||||||
shouldCast = '1',
|
shouldCast = '1',
|
||||||
shouldCastTrue = '2'
|
shouldCastTrue = '2'
|
||||||
|
@ -1,12 +1,21 @@
|
|||||||
import { EMPTY_OBJ } from './utils'
|
// import { EMPTY_OBJ } from './utils'
|
||||||
import { ComponentInstance } from './component'
|
import { ComponentInstance } from './component'
|
||||||
import { observable } from '@vue/observer'
|
import { observable } from '@vue/observer'
|
||||||
|
|
||||||
|
const internalRE = /^_|^\$/
|
||||||
|
|
||||||
export function initializeState(instance: ComponentInstance) {
|
export function initializeState(instance: ComponentInstance) {
|
||||||
if (instance.data) {
|
if (instance.data) {
|
||||||
instance._rawData = instance.data()
|
instance._rawData = instance.data()
|
||||||
instance.$data = observable(instance._rawData)
|
|
||||||
} else {
|
} 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 { createRenderProxy } from './componentProxy'
|
||||||
import { handleError, ErrorTypes } from './errorHandling'
|
import { handleError, ErrorTypes } from './errorHandling'
|
||||||
|
|
||||||
|
let currentVNode: VNode | null = null
|
||||||
|
let currentContextVNode: MountedVNode | null = null
|
||||||
|
|
||||||
export function createComponentInstance(
|
export function createComponentInstance(
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
Component: ComponentClass,
|
Component: ComponentClass,
|
||||||
contextVNode: MountedVNode | null
|
contextVNode: MountedVNode | null
|
||||||
): ComponentInstance {
|
): 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
|
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
|
// renderProxy
|
||||||
const proxy = (instance.$proxy = createRenderProxy(instance))
|
const proxy = (instance.$proxy = createRenderProxy(instance))
|
||||||
|
|
||||||
// pointer management
|
// parent chain management
|
||||||
if (contextVNode !== null) {
|
if (currentContextVNode !== null) {
|
||||||
// locate first non-functional parent
|
// locate first non-functional parent
|
||||||
while (
|
while (
|
||||||
contextVNode !== null &&
|
currentContextVNode !== null &&
|
||||||
contextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
|
currentContextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
|
||||||
contextVNode.contextVNode !== null
|
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
|
.children as ComponentInstance
|
||||||
instance.$parent = parentComponent.$proxy
|
instance.$parent = parentComponent.$proxy
|
||||||
instance.$root = parentComponent.$root
|
instance.$root = parentComponent.$root
|
||||||
@ -46,20 +80,15 @@ export function createComponentInstance(
|
|||||||
instance.$root = proxy
|
instance.$root = proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifecycle
|
// beforeCreate hook is called right in the constructor
|
||||||
if (instance.beforeCreate) {
|
if (instance.beforeCreate) {
|
||||||
instance.beforeCreate.call(proxy)
|
instance.beforeCreate.call(proxy)
|
||||||
}
|
}
|
||||||
initializeProps(instance, Component.props, vnode.data)
|
initializeProps(
|
||||||
initializeState(instance)
|
instance,
|
||||||
initializeComputed(instance, Component.computed)
|
instance.constructor.props,
|
||||||
initializeWatch(instance, Component.watch)
|
(currentVNode as VNode).data
|
||||||
instance.$slots = vnode.slots || EMPTY_OBJ
|
)
|
||||||
if (instance.created) {
|
|
||||||
instance.created.call(proxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance as ComponentInstance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
export function renderInstanceRoot(instance: ComponentInstance): VNode {
|
||||||
@ -218,11 +247,13 @@ export function createComponentClassFromOptions(
|
|||||||
export function resolveComponentOptions(
|
export function resolveComponentOptions(
|
||||||
Component: ComponentClass
|
Component: ComponentClass
|
||||||
): ComponentOptions {
|
): ComponentOptions {
|
||||||
const keys = Object.keys(Component)
|
const descriptors = Object.getOwnPropertyDescriptors(Component)
|
||||||
const options = {} as any
|
const options = {} as any
|
||||||
for (let i = 0; i < keys.length; i++) {
|
for (const key in descriptors) {
|
||||||
const key = keys[i]
|
const descriptor = descriptors[key]
|
||||||
options[key] = (Component as any)[key]
|
if (descriptor.enumerable || descriptor.get) {
|
||||||
|
options[key] = descriptor.get ? descriptor.get() : descriptor.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Component.computed = options.computed = resolveComputedOptions(Component)
|
Component.computed = options.computed = resolveComputedOptions(Component)
|
||||||
Component.options = options
|
Component.options = options
|
||||||
|
@ -18,13 +18,6 @@ interface AsyncComponentFullOptions {
|
|||||||
|
|
||||||
type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
|
type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
|
||||||
|
|
||||||
interface AsyncContainerData {
|
|
||||||
comp: ComponentType | null
|
|
||||||
err: Error | null
|
|
||||||
delayed: boolean
|
|
||||||
timedOut: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createAsyncComponent(
|
export function createAsyncComponent(
|
||||||
options: AsyncComponentOptions
|
options: AsyncComponentOptions
|
||||||
): ComponentClass {
|
): ComponentClass {
|
||||||
@ -40,15 +33,11 @@ export function createAsyncComponent(
|
|||||||
error: errorComp
|
error: errorComp
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
return class AsyncContainer extends Component<{}, AsyncContainerData> {
|
return class AsyncContainer extends Component {
|
||||||
data() {
|
comp: ComponentType | null = null
|
||||||
return {
|
err: Error | null = null
|
||||||
comp: null,
|
delayed: boolean = false
|
||||||
err: null,
|
timedOut: boolean = false
|
||||||
delayed: false,
|
|
||||||
timedOut: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// doing this in beforeMount so this is non-SSR only
|
// doing this in beforeMount so this is non-SSR only
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
|
@ -2,33 +2,21 @@ import {
|
|||||||
h,
|
h,
|
||||||
render,
|
render,
|
||||||
nextTick,
|
nextTick,
|
||||||
Component,
|
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
createComponentClassFromOptions
|
createComponentClassFromOptions
|
||||||
} from '@vue/renderer-dom'
|
} from '@vue/renderer-dom'
|
||||||
|
|
||||||
// Note: typing for this is intentionally loose, as it will be using 2.x types.
|
class Vue {
|
||||||
class Vue extends Component {
|
|
||||||
static h = h
|
static h = h
|
||||||
static render = render
|
static render = render
|
||||||
static nextTick = nextTick
|
static nextTick = nextTick
|
||||||
|
|
||||||
|
// Note: typing for this is intentionally loose, as it will be using 2.x types.
|
||||||
constructor(options: any) {
|
constructor(options: any) {
|
||||||
super()
|
// convert it to a class
|
||||||
if (!options) {
|
const Component = createComponentClassFromOptions(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)
|
|
||||||
const vnode = h(Component)
|
const vnode = h(Component)
|
||||||
// the component class is cached on the options object as ._normalized
|
|
||||||
const instance = createComponentInstance(vnode, Component, null)
|
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) {
|
function mount(el: any) {
|
||||||
const dom = typeof el === 'string' ? document.querySelector(el) : el
|
const dom = typeof el === 'string' ? document.querySelector(el) : el
|
||||||
|
Loading…
Reference in New Issue
Block a user