|
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
|
|
state,
|
|
|
|
|
immutableState
|
|
|
|
|
} from '@vue/reactivity'
|
|
|
|
|
import { EMPTY_OBJ, isFunction } from '@vue/shared'
|
|
|
|
|
import { EMPTY_OBJ, isFunction, capitalize, invokeHandlers } from '@vue/shared'
|
|
|
|
|
import { RenderProxyHandlers } from './componentProxy'
|
|
|
|
|
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
|
|
|
|
|
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
|
|
|
|
|
@@ -24,33 +24,26 @@ export type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
|
|
|
|
|
$slots: Data
|
|
|
|
|
$root: ComponentInstance | null
|
|
|
|
|
$parent: ComponentInstance | null
|
|
|
|
|
$emit: (event: string, ...args: any[]) => void
|
|
|
|
|
} & P &
|
|
|
|
|
S
|
|
|
|
|
|
|
|
|
|
type RenderFunction<P = Data> = (
|
|
|
|
|
props: P,
|
|
|
|
|
slots: Slots,
|
|
|
|
|
attrs: Data,
|
|
|
|
|
vnode: VNode
|
|
|
|
|
) => any
|
|
|
|
|
type SetupFunction<Props, RawBindings> = (
|
|
|
|
|
props: Props,
|
|
|
|
|
ctx: SetupContext
|
|
|
|
|
) => RawBindings | (() => VNodeChild)
|
|
|
|
|
|
|
|
|
|
type RenderFunctionWithThis<Props, RawBindings> = <
|
|
|
|
|
type RenderFunction<Props = {}, RawBindings = {}> = <
|
|
|
|
|
Bindings extends UnwrapValue<RawBindings>
|
|
|
|
|
>(
|
|
|
|
|
this: ComponentRenderProxy<Props, Bindings>,
|
|
|
|
|
props: Props,
|
|
|
|
|
slots: Slots,
|
|
|
|
|
attrs: Data,
|
|
|
|
|
vnode: VNode
|
|
|
|
|
ctx: ComponentRenderProxy<Props, Bindings>
|
|
|
|
|
) => VNodeChild
|
|
|
|
|
|
|
|
|
|
interface ComponentOptionsWithoutProps<Props = Data, RawBindings = Data> {
|
|
|
|
|
props?: undefined
|
|
|
|
|
setup?: (
|
|
|
|
|
this: ComponentRenderProxy<Props>,
|
|
|
|
|
props: Props
|
|
|
|
|
) => RawBindings | RenderFunction<Props>
|
|
|
|
|
render?: RenderFunctionWithThis<Props, RawBindings>
|
|
|
|
|
setup?: SetupFunction<Props, RawBindings>
|
|
|
|
|
render?: RenderFunction<Props, RawBindings>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ComponentOptionsWithArrayProps<
|
|
|
|
|
@@ -59,11 +52,8 @@ interface ComponentOptionsWithArrayProps<
|
|
|
|
|
Props = { [key in PropNames]?: any }
|
|
|
|
|
> {
|
|
|
|
|
props: PropNames[]
|
|
|
|
|
setup?: (
|
|
|
|
|
this: ComponentRenderProxy<Props>,
|
|
|
|
|
props: Props
|
|
|
|
|
) => RawBindings | RenderFunction<Props>
|
|
|
|
|
render?: RenderFunctionWithThis<Props, RawBindings>
|
|
|
|
|
setup?: SetupFunction<Props, RawBindings>
|
|
|
|
|
render?: RenderFunction<Props, RawBindings>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ComponentOptionsWithProps<
|
|
|
|
|
@@ -72,11 +62,8 @@ interface ComponentOptionsWithProps<
|
|
|
|
|
Props = ExtractPropTypes<PropsOptions>
|
|
|
|
|
> {
|
|
|
|
|
props: PropsOptions
|
|
|
|
|
setup?: (
|
|
|
|
|
this: ComponentRenderProxy<Props>,
|
|
|
|
|
props: Props
|
|
|
|
|
) => RawBindings | RenderFunction<Props>
|
|
|
|
|
render?: RenderFunctionWithThis<Props, RawBindings>
|
|
|
|
|
setup?: SetupFunction<Props, RawBindings>
|
|
|
|
|
render?: RenderFunction<Props, RawBindings>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ComponentOptions =
|
|
|
|
|
@@ -84,14 +71,15 @@ export type ComponentOptions =
|
|
|
|
|
| ComponentOptionsWithoutProps
|
|
|
|
|
| ComponentOptionsWithArrayProps
|
|
|
|
|
|
|
|
|
|
export interface FunctionalComponent<P = {}> extends RenderFunction<P> {
|
|
|
|
|
export interface FunctionalComponent<P = {}> {
|
|
|
|
|
(props: P, ctx: SetupContext): VNodeChild
|
|
|
|
|
props?: ComponentPropsOptions<P>
|
|
|
|
|
displayName?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type LifecycleHook = Function[] | null
|
|
|
|
|
|
|
|
|
|
export interface LifecycleHooks {
|
|
|
|
|
interface LifecycleHooks {
|
|
|
|
|
bm: LifecycleHook // beforeMount
|
|
|
|
|
m: LifecycleHook // mounted
|
|
|
|
|
bu: LifecycleHook // beforeUpdate
|
|
|
|
|
@@ -105,6 +93,13 @@ export interface LifecycleHooks {
|
|
|
|
|
ec: LifecycleHook // errorCaptured
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface SetupContext {
|
|
|
|
|
attrs: Data
|
|
|
|
|
slots: Slots
|
|
|
|
|
refs: Data
|
|
|
|
|
emit: ((event: string, ...args: any[]) => void)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ComponentInstance<P = Data, S = Data> = {
|
|
|
|
|
type: FunctionalComponent | ComponentOptions
|
|
|
|
|
parent: ComponentInstance | null
|
|
|
|
|
@@ -114,22 +109,22 @@ export type ComponentInstance<P = Data, S = Data> = {
|
|
|
|
|
subTree: VNode
|
|
|
|
|
update: ReactiveEffect
|
|
|
|
|
effects: ReactiveEffect[] | null
|
|
|
|
|
render: RenderFunction<P> | null
|
|
|
|
|
render: RenderFunction<P, S> | null
|
|
|
|
|
|
|
|
|
|
// the rest are only for stateful components
|
|
|
|
|
renderProxy: ComponentRenderProxy | null
|
|
|
|
|
propsProxy: Data | null
|
|
|
|
|
state: S
|
|
|
|
|
props: P
|
|
|
|
|
attrs: Data
|
|
|
|
|
slots: Slots
|
|
|
|
|
refs: Data
|
|
|
|
|
} & LifecycleHooks
|
|
|
|
|
renderProxy: ComponentRenderProxy | null
|
|
|
|
|
propsProxy: P | null
|
|
|
|
|
setupContext: SetupContext | null
|
|
|
|
|
} & SetupContext &
|
|
|
|
|
LifecycleHooks
|
|
|
|
|
|
|
|
|
|
// createComponent
|
|
|
|
|
// overload 1: direct setup function
|
|
|
|
|
// (uses user defined props interface)
|
|
|
|
|
export function createComponent<Props>(
|
|
|
|
|
setup: (props: Props) => RenderFunction<Props>
|
|
|
|
|
setup: (props: Props, ctx: SetupContext) => (() => any)
|
|
|
|
|
): (props: Props) => any
|
|
|
|
|
// overload 2: object format with no props
|
|
|
|
|
// (uses user defined props interface)
|
|
|
|
|
@@ -182,6 +177,7 @@ export function createComponentInstance(
|
|
|
|
|
render: null,
|
|
|
|
|
renderProxy: null,
|
|
|
|
|
propsProxy: null,
|
|
|
|
|
setupContext: null,
|
|
|
|
|
|
|
|
|
|
bm: null,
|
|
|
|
|
m: null,
|
|
|
|
|
@@ -201,7 +197,15 @@ export function createComponentInstance(
|
|
|
|
|
props: EMPTY_OBJ,
|
|
|
|
|
attrs: EMPTY_OBJ,
|
|
|
|
|
slots: EMPTY_OBJ,
|
|
|
|
|
refs: EMPTY_OBJ
|
|
|
|
|
refs: EMPTY_OBJ,
|
|
|
|
|
|
|
|
|
|
emit: (event: string, ...args: any[]) => {
|
|
|
|
|
const props = instance.vnode.props || EMPTY_OBJ
|
|
|
|
|
const handler = props[`on${event}`] || props[`on${capitalize(event)}`]
|
|
|
|
|
if (handler) {
|
|
|
|
|
invokeHandlers(handler, args)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
instance.root = parent ? parent.root : instance
|
|
|
|
|
@@ -223,12 +227,13 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|
|
|
|
currentInstance = instance
|
|
|
|
|
// the props proxy makes the props object passed to setup() reactive
|
|
|
|
|
// so props change can be tracked by watchers
|
|
|
|
|
// only need to create it if setup() actually expects it
|
|
|
|
|
// it will be updated in resolveProps() on updates before render
|
|
|
|
|
const propsProxy = (instance.propsProxy = setup.length
|
|
|
|
|
? immutableState(instance.props)
|
|
|
|
|
: null)
|
|
|
|
|
const setupResult = setup.call(proxy, propsProxy)
|
|
|
|
|
const setupContext = (instance.setupContext =
|
|
|
|
|
setup.length > 1 ? createSetupContext(instance) : null)
|
|
|
|
|
const setupResult = setup.call(proxy, propsProxy, setupContext)
|
|
|
|
|
if (isFunction(setupResult)) {
|
|
|
|
|
// setup returned an inline render function
|
|
|
|
|
instance.render = setupResult
|
|
|
|
|
@@ -245,22 +250,55 @@ export function setupStatefulComponent(instance: ComponentInstance) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const SetupProxyHandlers: { [key: string]: ProxyHandler<any> } = {}
|
|
|
|
|
;['attrs', 'slots', 'refs'].forEach((type: string) => {
|
|
|
|
|
SetupProxyHandlers[type] = {
|
|
|
|
|
get: (instance: any, key: string) => (instance[type] as any)[key],
|
|
|
|
|
has: (instance: any, key: string) => key in (instance[type] as any),
|
|
|
|
|
ownKeys: (instance: any) => Object.keys(instance[type] as any),
|
|
|
|
|
set: () => false,
|
|
|
|
|
deleteProperty: () => false
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function createSetupContext(instance: ComponentInstance): SetupContext {
|
|
|
|
|
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),
|
|
|
|
|
emit: instance.emit
|
|
|
|
|
} as any
|
|
|
|
|
return __DEV__ ? Object.freeze(context) : context
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function renderComponentRoot(instance: ComponentInstance): VNode {
|
|
|
|
|
const { type: Component, renderProxy, props, slots, attrs, vnode } = instance
|
|
|
|
|
if (vnode.shapeFlag & STATEFUL_COMPONENT) {
|
|
|
|
|
return normalizeVNode(
|
|
|
|
|
(instance.render as RenderFunction).call(
|
|
|
|
|
const {
|
|
|
|
|
type: Component,
|
|
|
|
|
vnode,
|
|
|
|
|
renderProxy,
|
|
|
|
|
setupContext,
|
|
|
|
|
props,
|
|
|
|
|
slots,
|
|
|
|
|
attrs,
|
|
|
|
|
vnode
|
|
|
|
|
)
|
|
|
|
|
refs,
|
|
|
|
|
emit
|
|
|
|
|
} = instance
|
|
|
|
|
if (vnode.shapeFlag & STATEFUL_COMPONENT) {
|
|
|
|
|
return normalizeVNode(
|
|
|
|
|
(instance.render as RenderFunction).call(renderProxy, props, setupContext)
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
// functional
|
|
|
|
|
return normalizeVNode(
|
|
|
|
|
(Component as FunctionalComponent)(props, slots, attrs, vnode)
|
|
|
|
|
(Component as FunctionalComponent)(props, {
|
|
|
|
|
attrs,
|
|
|
|
|
slots,
|
|
|
|
|
refs,
|
|
|
|
|
emit
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|