vue3-yuanma/packages/runtime-core/src/component.ts

376 lines
10 KiB
TypeScript
Raw Normal View History

import { VNode, VNodeChild, isVNode } from './vnode'
import { ReactiveEffect, reactive, readonly } from '@vue/reactivity'
2019-09-07 00:58:31 +08:00
import {
PublicInstanceProxyHandlers,
ComponentPublicInstance
} from './componentPublicInstanceProxy'
import { ComponentPropsOptions } from './componentProps'
2019-05-31 18:07:43 +08:00
import { Slots } from './componentSlots'
import { warn } from './warning'
import {
2019-09-07 00:58:31 +08:00
ErrorCodes,
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'
import {
EMPTY_OBJ,
isFunction,
capitalize,
NOOP,
isArray,
isObject
} from '@vue/shared'
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
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
}
type Emit = ((event: string, ...args: unknown[]) => void)
export interface SetupContext {
2019-06-19 16:43:34 +08:00
attrs: Data
slots: Slots
emit: Emit
2019-06-19 16:43:34 +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
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
renderContext: Data
data: Data
props: Data
attrs: Data
slots: Slots
2019-09-07 00:58:31 +08:00
renderProxy: ComponentPublicInstance | null
propsProxy: Data | null
2019-06-19 16:43:34 +08:00
setupContext: SetupContext | null
refs: Data
emit: Emit
2019-08-21 21:50:20 +08:00
// user namespace
user: { [key: string]: any }
// lifecycle
isUnmounted: boolean
[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-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,
render: null,
2019-05-30 23:16:15 +08:00
renderProxy: null,
propsProxy: null,
2019-06-19 16:43:34 +08:00
setupContext: null,
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
// setup context properties
renderContext: EMPTY_OBJ,
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,
// user namespace for storing whatever the user assigns to `this`
user: {},
// lifecycle hooks
// not using enums here because it results in computed properties
isUnmounted: false,
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) {
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,
args
)
}
} else {
callWithAsyncErrorHandling(
handler,
instance,
2019-09-07 00:58:31 +08:00
ErrorCodes.COMPONENT_EVENT_HANDLER,
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
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
) => {
currentInstance = instance
}
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
// 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
currentSuspense = parentSuspense
const setupResult = callWithErrorHandling(
setup,
instance,
2019-09-07 00:58:31 +08:00
ErrorCodes.SETUP_FUNCTION,
[propsProxy, setupContext]
)
2019-09-10 04:00:50 +08:00
currentInstance = null
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
} else {
handleSetupResult(instance, setupResult, parentSuspense)
}
2019-08-22 05:05:14 +08:00
} else {
finishComponentSetup(instance, parentSuspense)
2019-09-10 04:00:50 +08:00
}
}
export function handleSetupResult(
instance: ComponentInternalInstance,
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)) {
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
}`
)
}
finishComponentSetup(instance, parentSuspense)
2019-09-10 04:00:50 +08:00
}
2019-09-20 12:24:16 +08:00
let compile: Function | undefined
export function registerCompiler(_compile: Function) {
compile = _compile
}
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-09-20 12:24:16 +08:00
if (Component.template && !Component.render) {
if (compile) {
Component.render = compile(Component.template)
} else if (__DEV__) {
warn(
`Component provides template but the build of Vue you are running ` +
`does not support on-the-fly template compilation. Either use the ` +
`full build or pre-compile the template using Vue CLI.`
)
}
}
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
currentSuspense = parentSuspense
2019-09-04 23:36:27 +08:00
applyOptions(instance, Component)
2019-09-10 04:00:50 +08:00
currentInstance = null
currentSuspense = null
2019-09-04 10:25:38 +08:00
}
2019-09-10 04:00:50 +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
}