2019-09-12 11:44:37 +08:00
|
|
|
import { VNode, VNodeChild, isVNode } from './vnode'
|
2019-09-06 23:19:22 +08:00
|
|
|
import { ReactiveEffect, reactive, readonly } from '@vue/reactivity'
|
2019-09-07 00:58:31 +08:00
|
|
|
import {
|
|
|
|
PublicInstanceProxyHandlers,
|
|
|
|
ComponentPublicInstance
|
|
|
|
} from './componentPublicInstanceProxy'
|
2019-09-06 23:19:22 +08:00
|
|
|
import { ComponentPropsOptions } from './componentProps'
|
2019-05-31 18:07:43 +08:00
|
|
|
import { Slots } from './componentSlots'
|
2019-08-31 03:05:39 +08:00
|
|
|
import { warn } from './warning'
|
|
|
|
import {
|
2019-09-07 00:58:31 +08:00
|
|
|
ErrorCodes,
|
2019-08-31 03:05:39 +08:00
|
|
|
callWithErrorHandling,
|
|
|
|
callWithAsyncErrorHandling
|
|
|
|
} from './errorHandling'
|
2019-09-04 23:36:27 +08:00
|
|
|
import { AppContext, createAppContext } from './apiApp'
|
2019-09-04 06:11:04 +08:00
|
|
|
import { Directive } from './directives'
|
2019-09-06 23:25:11 +08:00
|
|
|
import { applyOptions, ComponentOptions } from './componentOptions'
|
2019-09-06 23:19:22 +08:00
|
|
|
import {
|
|
|
|
EMPTY_OBJ,
|
|
|
|
isFunction,
|
|
|
|
capitalize,
|
|
|
|
NOOP,
|
|
|
|
isArray,
|
|
|
|
isObject
|
|
|
|
} from '@vue/shared'
|
2019-09-11 22:09:00 +08:00
|
|
|
import { SuspenseBoundary } from './suspense'
|
2019-05-28 13:27:31 +08:00
|
|
|
|
2019-08-13 23:18:23 +08:00
|
|
|
export type Data = { [key: string]: unknown }
|
2019-05-29 10:43:27 +08:00
|
|
|
|
2019-06-19 16:43:34 +08:00
|
|
|
export interface FunctionalComponent<P = {}> {
|
|
|
|
(props: P, ctx: SetupContext): VNodeChild
|
2019-05-28 18:06:00 +08:00
|
|
|
props?: ComponentPropsOptions<P>
|
|
|
|
displayName?: string
|
|
|
|
}
|
|
|
|
|
2019-09-03 04:09:34 +08:00
|
|
|
export type Component = ComponentOptions | FunctionalComponent
|
|
|
|
|
2019-05-28 19:36:15 +08:00
|
|
|
type LifecycleHook = Function[] | null
|
|
|
|
|
2019-08-31 00:16:09 +08:00
|
|
|
export const enum LifecycleHooks {
|
|
|
|
BEFORE_CREATE = 'bc',
|
|
|
|
CREATED = 'c',
|
|
|
|
BEFORE_MOUNT = 'bm',
|
|
|
|
MOUNTED = 'm',
|
|
|
|
BEFORE_UPDATE = 'bu',
|
|
|
|
UPDATED = 'u',
|
|
|
|
BEFORE_UNMOUNT = 'bum',
|
|
|
|
UNMOUNTED = 'um',
|
|
|
|
DEACTIVATED = 'da',
|
|
|
|
ACTIVATED = 'a',
|
|
|
|
RENDER_TRIGGERED = 'rtg',
|
|
|
|
RENDER_TRACKED = 'rtc',
|
|
|
|
ERROR_CAPTURED = 'ec'
|
2019-05-28 19:36:15 +08:00
|
|
|
}
|
|
|
|
|
2019-09-06 08:36:35 +08:00
|
|
|
type Emit = ((event: string, ...args: unknown[]) => void)
|
|
|
|
|
2019-09-06 23:19:22 +08:00
|
|
|
export interface SetupContext {
|
2019-06-19 16:43:34 +08:00
|
|
|
attrs: Data
|
|
|
|
slots: Slots
|
2019-09-06 08:36:35 +08:00
|
|
|
emit: Emit
|
2019-06-19 16:43:34 +08:00
|
|
|
}
|
|
|
|
|
2019-09-06 06:48:49 +08:00
|
|
|
type RenderFunction = () => VNodeChild
|
|
|
|
|
2019-09-07 00:58:31 +08:00
|
|
|
export interface ComponentInternalInstance {
|
2019-05-28 18:06:00 +08:00
|
|
|
type: FunctionalComponent | ComponentOptions
|
2019-09-07 00:58:31 +08:00
|
|
|
parent: ComponentInternalInstance | null
|
2019-09-03 04:09:34 +08:00
|
|
|
appContext: AppContext
|
2019-09-07 00:58:31 +08:00
|
|
|
root: ComponentInternalInstance
|
2019-05-29 10:43:27 +08:00
|
|
|
vnode: VNode
|
2019-05-28 17:19:47 +08:00
|
|
|
next: VNode | null
|
2019-05-29 10:43:27 +08:00
|
|
|
subTree: VNode
|
2019-05-28 17:19:47 +08:00
|
|
|
update: ReactiveEffect
|
2019-09-06 06:48:49 +08:00
|
|
|
render: RenderFunction | null
|
2019-06-19 17:31:49 +08:00
|
|
|
effects: ReactiveEffect[] | null
|
2019-06-19 22:48:22 +08:00
|
|
|
provides: Data
|
2019-06-19 16:43:34 +08:00
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
components: Record<string, Component>
|
|
|
|
directives: Record<string, Directive>
|
|
|
|
|
2019-09-10 04:00:50 +08:00
|
|
|
asyncDep: Promise<any> | null
|
|
|
|
asyncResult: any
|
|
|
|
asyncResolved: boolean
|
|
|
|
|
2019-05-29 10:43:27 +08:00
|
|
|
// the rest are only for stateful components
|
2019-09-06 08:36:35 +08:00
|
|
|
renderContext: Data
|
|
|
|
data: Data
|
|
|
|
props: Data
|
|
|
|
attrs: Data
|
|
|
|
slots: Slots
|
2019-09-07 00:58:31 +08:00
|
|
|
renderProxy: ComponentPublicInstance | null
|
2019-09-06 08:36:35 +08:00
|
|
|
propsProxy: Data | null
|
2019-06-19 16:43:34 +08:00
|
|
|
setupContext: SetupContext | null
|
2019-09-04 03:27:59 +08:00
|
|
|
refs: Data
|
2019-09-06 08:36:35 +08:00
|
|
|
emit: Emit
|
2019-08-21 21:50:20 +08:00
|
|
|
|
|
|
|
// user namespace
|
|
|
|
user: { [key: string]: any }
|
2019-08-31 00:16:09 +08:00
|
|
|
|
|
|
|
// lifecycle
|
2019-09-11 21:07:29 +08:00
|
|
|
isUnmounted: boolean
|
2019-08-31 00:16:09 +08:00
|
|
|
[LifecycleHooks.BEFORE_CREATE]: LifecycleHook
|
|
|
|
[LifecycleHooks.CREATED]: LifecycleHook
|
|
|
|
[LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
|
|
|
|
[LifecycleHooks.MOUNTED]: LifecycleHook
|
|
|
|
[LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
|
|
|
|
[LifecycleHooks.UPDATED]: LifecycleHook
|
|
|
|
[LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
|
|
|
|
[LifecycleHooks.UNMOUNTED]: LifecycleHook
|
|
|
|
[LifecycleHooks.RENDER_TRACKED]: LifecycleHook
|
|
|
|
[LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
|
|
|
|
[LifecycleHooks.ACTIVATED]: LifecycleHook
|
|
|
|
[LifecycleHooks.DEACTIVATED]: LifecycleHook
|
|
|
|
[LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
|
2019-09-06 08:36:35 +08:00
|
|
|
}
|
2019-05-28 19:36:15 +08:00
|
|
|
|
2019-09-03 04:09:34 +08:00
|
|
|
const emptyAppContext = createAppContext()
|
|
|
|
|
2019-06-03 09:43:28 +08:00
|
|
|
export function createComponentInstance(
|
2019-08-29 00:13:36 +08:00
|
|
|
vnode: VNode,
|
2019-09-07 00:58:31 +08:00
|
|
|
parent: ComponentInternalInstance | null
|
|
|
|
): ComponentInternalInstance {
|
2019-09-04 06:11:04 +08:00
|
|
|
// inherit parent app context - or - if root, adopt from root vnode
|
|
|
|
const appContext =
|
|
|
|
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
|
2019-06-03 09:43:28 +08:00
|
|
|
const instance = {
|
2019-08-29 00:13:36 +08:00
|
|
|
vnode,
|
2019-06-03 09:43:28 +08:00
|
|
|
parent,
|
2019-09-04 06:11:04 +08:00
|
|
|
appContext,
|
2019-09-04 23:36:27 +08:00
|
|
|
type: vnode.type as Component,
|
2019-06-03 09:43:28 +08:00
|
|
|
root: null as any, // set later so it can point to itself
|
2019-05-28 19:36:15 +08:00
|
|
|
next: null,
|
2019-05-29 10:43:27 +08:00
|
|
|
subTree: null as any,
|
2019-05-28 19:36:15 +08:00
|
|
|
update: null as any,
|
2019-06-12 15:43:19 +08:00
|
|
|
render: null,
|
2019-05-30 23:16:15 +08:00
|
|
|
renderProxy: null,
|
|
|
|
propsProxy: null,
|
2019-06-19 16:43:34 +08:00
|
|
|
setupContext: null,
|
2019-08-31 00:16:09 +08:00
|
|
|
effects: null,
|
2019-09-04 06:11:04 +08:00
|
|
|
provides: parent ? parent.provides : Object.create(appContext.provides),
|
2019-05-28 19:36:15 +08:00
|
|
|
|
2019-08-31 00:16:09 +08:00
|
|
|
// setup context properties
|
2019-09-06 08:36:35 +08:00
|
|
|
renderContext: EMPTY_OBJ,
|
2019-08-31 00:16:09 +08:00
|
|
|
data: EMPTY_OBJ,
|
|
|
|
props: EMPTY_OBJ,
|
|
|
|
attrs: EMPTY_OBJ,
|
|
|
|
slots: EMPTY_OBJ,
|
|
|
|
refs: EMPTY_OBJ,
|
|
|
|
|
2019-09-04 23:36:27 +08:00
|
|
|
// per-instance asset storage (mutable during options resolution)
|
|
|
|
components: Object.create(appContext.components),
|
|
|
|
directives: Object.create(appContext.directives),
|
|
|
|
|
2019-09-10 04:00:50 +08:00
|
|
|
// async dependency management
|
|
|
|
asyncDep: null,
|
|
|
|
asyncResult: null,
|
|
|
|
asyncResolved: false,
|
|
|
|
|
2019-08-31 00:16:09 +08:00
|
|
|
// user namespace for storing whatever the user assigns to `this`
|
|
|
|
user: {},
|
|
|
|
|
|
|
|
// lifecycle hooks
|
|
|
|
// not using enums here because it results in computed properties
|
2019-09-11 21:07:29 +08:00
|
|
|
isUnmounted: false,
|
2019-08-31 00:16:09 +08:00
|
|
|
bc: null,
|
|
|
|
c: null,
|
2019-05-28 19:36:15 +08:00
|
|
|
bm: null,
|
|
|
|
m: null,
|
|
|
|
bu: null,
|
|
|
|
u: null,
|
|
|
|
um: null,
|
|
|
|
bum: null,
|
|
|
|
da: null,
|
|
|
|
a: null,
|
|
|
|
rtg: null,
|
|
|
|
rtc: null,
|
|
|
|
ec: null,
|
2019-08-21 21:50:20 +08:00
|
|
|
|
2019-08-13 23:18:23 +08:00
|
|
|
emit: (event: string, ...args: unknown[]) => {
|
2019-06-19 16:43:34 +08:00
|
|
|
const props = instance.vnode.props || EMPTY_OBJ
|
|
|
|
const handler = props[`on${event}`] || props[`on${capitalize(event)}`]
|
|
|
|
if (handler) {
|
2019-08-31 03:05:39 +08:00
|
|
|
if (isArray(handler)) {
|
|
|
|
for (let i = 0; i < handler.length; i++) {
|
|
|
|
callWithAsyncErrorHandling(
|
|
|
|
handler[i],
|
|
|
|
instance,
|
2019-09-07 00:58:31 +08:00
|
|
|
ErrorCodes.COMPONENT_EVENT_HANDLER,
|
2019-08-31 03:05:39 +08:00
|
|
|
args
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
callWithAsyncErrorHandling(
|
|
|
|
handler,
|
|
|
|
instance,
|
2019-09-07 00:58:31 +08:00
|
|
|
ErrorCodes.COMPONENT_EVENT_HANDLER,
|
2019-08-31 03:05:39 +08:00
|
|
|
args
|
|
|
|
)
|
|
|
|
}
|
2019-06-19 16:43:34 +08:00
|
|
|
}
|
|
|
|
}
|
2019-05-28 19:36:15 +08:00
|
|
|
}
|
2019-06-03 09:43:28 +08:00
|
|
|
|
|
|
|
instance.root = parent ? parent.root : instance
|
|
|
|
return instance
|
2019-05-28 19:36:15 +08:00
|
|
|
}
|
|
|
|
|
2019-09-07 00:58:31 +08:00
|
|
|
export let currentInstance: ComponentInternalInstance | null = null
|
2019-09-11 22:09:00 +08:00
|
|
|
export let currentSuspense: SuspenseBoundary | null = null
|
2019-05-28 19:36:15 +08:00
|
|
|
|
2019-09-07 00:58:31 +08:00
|
|
|
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
2019-06-20 15:25:10 +08:00
|
|
|
currentInstance
|
|
|
|
|
2019-09-07 00:58:31 +08:00
|
|
|
export const setCurrentInstance = (
|
|
|
|
instance: ComponentInternalInstance | null
|
|
|
|
) => {
|
2019-08-31 00:16:09 +08:00
|
|
|
currentInstance = instance
|
|
|
|
}
|
|
|
|
|
2019-09-11 22:09:00 +08:00
|
|
|
export function setupStatefulComponent(
|
|
|
|
instance: ComponentInternalInstance,
|
|
|
|
parentSuspense: SuspenseBoundary | null
|
|
|
|
) {
|
2019-05-29 10:43:27 +08:00
|
|
|
const Component = instance.type as ComponentOptions
|
2019-05-28 19:36:15 +08:00
|
|
|
// 1. create render proxy
|
2019-09-07 00:58:31 +08:00
|
|
|
instance.renderProxy = new Proxy(instance, PublicInstanceProxyHandlers) as any
|
2019-09-04 03:27:59 +08:00
|
|
|
// 2. create props proxy
|
|
|
|
// the propsProxy is a reactive AND readonly proxy to the actual props.
|
|
|
|
// it will be updated in resolveProps() on updates before render
|
|
|
|
const propsProxy = (instance.propsProxy = readonly(instance.props))
|
|
|
|
// 3. call setup()
|
2019-05-30 23:16:15 +08:00
|
|
|
const { setup } = Component
|
|
|
|
if (setup) {
|
2019-06-19 16:43:34 +08:00
|
|
|
const setupContext = (instance.setupContext =
|
|
|
|
setup.length > 1 ? createSetupContext(instance) : null)
|
2019-09-10 04:00:50 +08:00
|
|
|
|
|
|
|
currentInstance = instance
|
2019-09-11 22:09:00 +08:00
|
|
|
currentSuspense = parentSuspense
|
2019-08-31 03:05:39 +08:00
|
|
|
const setupResult = callWithErrorHandling(
|
|
|
|
setup,
|
|
|
|
instance,
|
2019-09-07 00:58:31 +08:00
|
|
|
ErrorCodes.SETUP_FUNCTION,
|
2019-08-31 03:05:39 +08:00
|
|
|
[propsProxy, setupContext]
|
|
|
|
)
|
2019-09-10 04:00:50 +08:00
|
|
|
currentInstance = null
|
2019-09-11 22:09:00 +08:00
|
|
|
currentSuspense = null
|
2019-06-19 22:48:22 +08:00
|
|
|
|
2019-09-10 04:00:50 +08:00
|
|
|
if (
|
|
|
|
setupResult &&
|
|
|
|
isFunction(setupResult.then) &&
|
|
|
|
isFunction(setupResult.catch)
|
|
|
|
) {
|
2019-09-10 04:28:32 +08:00
|
|
|
if (__FEATURE_SUSPENSE__) {
|
|
|
|
// async setup returned Promise.
|
|
|
|
// bail here and wait for re-entry.
|
|
|
|
instance.asyncDep = setupResult as Promise<any>
|
|
|
|
} else if (__DEV__) {
|
|
|
|
warn(
|
|
|
|
`setup() returned a Promise, but the version of Vue you are using ` +
|
|
|
|
`does not support it yet.`
|
|
|
|
)
|
|
|
|
}
|
2019-09-10 04:00:50 +08:00
|
|
|
return
|
2019-06-12 15:43:19 +08:00
|
|
|
} else {
|
2019-09-11 22:09:00 +08:00
|
|
|
handleSetupResult(instance, setupResult, parentSuspense)
|
2019-06-12 15:43:19 +08:00
|
|
|
}
|
2019-08-22 05:05:14 +08:00
|
|
|
} else {
|
2019-09-11 22:09:00 +08:00
|
|
|
finishComponentSetup(instance, parentSuspense)
|
2019-09-10 04:00:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function handleSetupResult(
|
|
|
|
instance: ComponentInternalInstance,
|
2019-09-11 22:09:00 +08:00
|
|
|
setupResult: unknown,
|
|
|
|
parentSuspense: SuspenseBoundary | null
|
2019-09-10 04:00:50 +08:00
|
|
|
) {
|
|
|
|
if (isFunction(setupResult)) {
|
|
|
|
// setup returned an inline render function
|
|
|
|
instance.render = setupResult as RenderFunction
|
|
|
|
} else if (isObject(setupResult)) {
|
2019-09-12 11:44:37 +08:00
|
|
|
if (__DEV__ && isVNode(setupResult)) {
|
|
|
|
warn(
|
|
|
|
`setup() should not return VNodes directly - ` +
|
|
|
|
`return a render function instead.`
|
|
|
|
)
|
|
|
|
}
|
2019-09-10 04:00:50 +08:00
|
|
|
// setup returned bindings.
|
|
|
|
// assuming a render function compiled from template is present.
|
|
|
|
instance.renderContext = reactive(setupResult)
|
|
|
|
} else if (__DEV__ && setupResult !== undefined) {
|
|
|
|
warn(
|
|
|
|
`setup() should return an object. Received: ${
|
|
|
|
setupResult === null ? 'null' : typeof setupResult
|
|
|
|
}`
|
|
|
|
)
|
|
|
|
}
|
2019-09-11 22:09:00 +08:00
|
|
|
finishComponentSetup(instance, parentSuspense)
|
2019-09-10 04:00:50 +08:00
|
|
|
}
|
|
|
|
|
2019-09-11 22:09:00 +08:00
|
|
|
function finishComponentSetup(
|
|
|
|
instance: ComponentInternalInstance,
|
|
|
|
parentSuspense: SuspenseBoundary | null
|
|
|
|
) {
|
2019-09-10 04:00:50 +08:00
|
|
|
const Component = instance.type as ComponentOptions
|
|
|
|
if (!instance.render) {
|
2019-08-22 05:05:14 +08:00
|
|
|
if (__DEV__ && !Component.render) {
|
2019-09-04 10:25:38 +08:00
|
|
|
warn(
|
|
|
|
`Component is missing render function. Either provide a template or ` +
|
|
|
|
`return a render function from setup().`
|
|
|
|
)
|
2019-08-22 05:05:14 +08:00
|
|
|
}
|
2019-09-10 04:00:50 +08:00
|
|
|
instance.render = (Component.render || NOOP) as RenderFunction
|
2019-05-28 19:36:15 +08:00
|
|
|
}
|
2019-09-10 04:00:50 +08:00
|
|
|
|
2019-09-04 10:25:38 +08:00
|
|
|
// support for 2.x options
|
|
|
|
if (__FEATURE_OPTIONS__) {
|
2019-09-10 04:00:50 +08:00
|
|
|
currentInstance = instance
|
2019-09-11 22:09:00 +08:00
|
|
|
currentSuspense = parentSuspense
|
2019-09-04 23:36:27 +08:00
|
|
|
applyOptions(instance, Component)
|
2019-09-10 04:00:50 +08:00
|
|
|
currentInstance = null
|
2019-09-11 22:09:00 +08:00
|
|
|
currentSuspense = null
|
2019-09-04 10:25:38 +08:00
|
|
|
}
|
2019-09-10 04:00:50 +08:00
|
|
|
|
2019-09-06 08:36:35 +08:00
|
|
|
if (instance.renderContext === EMPTY_OBJ) {
|
|
|
|
instance.renderContext = reactive({})
|
2019-09-05 06:16:11 +08:00
|
|
|
}
|
2019-05-28 19:36:15 +08:00
|
|
|
}
|
2019-05-28 17:19:47 +08:00
|
|
|
|
2019-08-23 10:07:51 +08:00
|
|
|
// used to identify a setup context proxy
|
|
|
|
export const SetupProxySymbol = Symbol()
|
|
|
|
|
2019-06-19 16:43:34 +08:00
|
|
|
const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
|
|
|
|
;['attrs', 'slots', 'refs'].forEach((type: string) => {
|
|
|
|
SetupProxyHandlers[type] = {
|
2019-08-23 10:07:51 +08:00
|
|
|
get: (instance, key) => (instance[type] as any)[key],
|
|
|
|
has: (instance, key) =>
|
|
|
|
key === SetupProxySymbol || key in (instance[type] as any),
|
|
|
|
ownKeys: instance => Reflect.ownKeys(instance[type] as any),
|
|
|
|
// this is necessary for ownKeys to work properly
|
|
|
|
getOwnPropertyDescriptor: (instance, key) =>
|
|
|
|
Reflect.getOwnPropertyDescriptor(instance[type], key),
|
2019-06-19 16:43:34 +08:00
|
|
|
set: () => false,
|
|
|
|
deleteProperty: () => false
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-09-07 00:58:31 +08:00
|
|
|
function createSetupContext(instance: ComponentInternalInstance): SetupContext {
|
2019-06-19 16:43:34 +08:00
|
|
|
const context = {
|
|
|
|
// attrs, slots & refs are non-reactive, but they need to always expose
|
|
|
|
// the latest values (instance.xxx may get replaced during updates) so we
|
|
|
|
// need to expose them through a proxy
|
|
|
|
attrs: new Proxy(instance, SetupProxyHandlers.attrs),
|
|
|
|
slots: new Proxy(instance, SetupProxyHandlers.slots),
|
|
|
|
refs: new Proxy(instance, SetupProxyHandlers.refs),
|
2019-08-27 06:08:56 +08:00
|
|
|
emit: instance.emit
|
2019-06-19 16:43:34 +08:00
|
|
|
} as any
|
|
|
|
return __DEV__ ? Object.freeze(context) : context
|
|
|
|
}
|