refactor: component setup flow
This commit is contained in:
parent
dbbb36bd76
commit
6ceb732114
@ -2,12 +2,7 @@ import { VNode, normalizeVNode, VNodeChild } from './vnode'
|
|||||||
import { ReactiveEffect } from '@vue/observer'
|
import { ReactiveEffect } from '@vue/observer'
|
||||||
import { isFunction, EMPTY_OBJ } from '@vue/shared'
|
import { isFunction, EMPTY_OBJ } from '@vue/shared'
|
||||||
import { RenderProxyHandlers } from './componentProxy'
|
import { RenderProxyHandlers } from './componentProxy'
|
||||||
import {
|
import { ComponentPropsOptions, PropValidator } from './componentProps'
|
||||||
resolveProps,
|
|
||||||
ComponentPropsOptions,
|
|
||||||
initializeProps,
|
|
||||||
PropValidator
|
|
||||||
} from './componentProps'
|
|
||||||
|
|
||||||
interface Value<T> {
|
interface Value<T> {
|
||||||
value: T
|
value: T
|
||||||
@ -37,13 +32,6 @@ export interface ComponentPublicProperties<P = Data, S = Data> {
|
|||||||
$slots: Data
|
$slots: Data
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RenderFunctionArg<B = Data, P = Data> {
|
|
||||||
state: B
|
|
||||||
props: P
|
|
||||||
attrs: Data
|
|
||||||
slots: Slots
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ComponentOptions<
|
export interface ComponentOptions<
|
||||||
RawProps = ComponentPropsOptions,
|
RawProps = ComponentPropsOptions,
|
||||||
RawBindings = Data | void,
|
RawBindings = Data | void,
|
||||||
@ -54,22 +42,16 @@ export interface ComponentOptions<
|
|||||||
setup?: (props: Props) => RawBindings
|
setup?: (props: Props) => RawBindings
|
||||||
render?: <B extends Bindings>(
|
render?: <B extends Bindings>(
|
||||||
this: ComponentPublicProperties<Props, B>,
|
this: ComponentPublicProperties<Props, B>,
|
||||||
ctx: RenderFunctionArg<B, Props>
|
ctx: ComponentInstance<Props, B>
|
||||||
) => VNodeChild
|
) => VNodeChild
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunctionalComponent<P = {}> {
|
export interface FunctionalComponent<P = {}> {
|
||||||
(ctx: RenderFunctionArg): any
|
(ctx: ComponentInstance<P>): any
|
||||||
props?: ComponentPropsOptions<P>
|
props?: ComponentPropsOptions<P>
|
||||||
displayName?: string
|
displayName?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Slot = (...args: any[]) => VNode[]
|
|
||||||
|
|
||||||
export type Slots = Readonly<{
|
|
||||||
[name: string]: Slot
|
|
||||||
}>
|
|
||||||
|
|
||||||
type LifecycleHook = Function[] | null
|
type LifecycleHook = Function[] | null
|
||||||
|
|
||||||
export interface LifecycleHooks {
|
export interface LifecycleHooks {
|
||||||
@ -86,18 +68,26 @@ export interface LifecycleHooks {
|
|||||||
ec: LifecycleHook // errorCaptured
|
ec: LifecycleHook // errorCaptured
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentInstance = {
|
export type Slot = (...args: any[]) => VNode[]
|
||||||
|
|
||||||
|
export type Slots = Readonly<{
|
||||||
|
[name: string]: Slot
|
||||||
|
}>
|
||||||
|
|
||||||
|
export type ComponentInstance<P = Data, S = Data> = {
|
||||||
type: FunctionalComponent | ComponentOptions
|
type: FunctionalComponent | ComponentOptions
|
||||||
vnode: VNode
|
vnode: VNode
|
||||||
next: VNode | null
|
next: VNode | null
|
||||||
subTree: VNode
|
subTree: VNode
|
||||||
update: ReactiveEffect
|
update: ReactiveEffect
|
||||||
|
|
||||||
// the rest are only for stateful components
|
// the rest are only for stateful components
|
||||||
bindings: Data | null
|
proxy: ComponentPublicProperties | null
|
||||||
proxy: Data | null
|
state: S
|
||||||
} & LifecycleHooks &
|
props: P
|
||||||
ComponentPublicProperties
|
attrs: Data
|
||||||
|
slots: Slots
|
||||||
|
refs: Data
|
||||||
|
} & LifecycleHooks
|
||||||
|
|
||||||
// no-op, for type inference only
|
// no-op, for type inference only
|
||||||
export function createComponent<
|
export function createComponent<
|
||||||
@ -121,7 +111,6 @@ export function createComponentInstance(type: any): ComponentInstance {
|
|||||||
next: null,
|
next: null,
|
||||||
subTree: null as any,
|
subTree: null as any,
|
||||||
update: null as any,
|
update: null as any,
|
||||||
bindings: null,
|
|
||||||
proxy: null,
|
proxy: null,
|
||||||
|
|
||||||
bm: null,
|
bm: null,
|
||||||
@ -137,64 +126,41 @@ export function createComponentInstance(type: any): ComponentInstance {
|
|||||||
ec: null,
|
ec: null,
|
||||||
|
|
||||||
// public properties
|
// public properties
|
||||||
$attrs: EMPTY_OBJ,
|
state: EMPTY_OBJ,
|
||||||
$props: EMPTY_OBJ,
|
props: EMPTY_OBJ,
|
||||||
$refs: EMPTY_OBJ,
|
attrs: EMPTY_OBJ,
|
||||||
$slots: EMPTY_OBJ,
|
slots: EMPTY_OBJ,
|
||||||
$state: EMPTY_OBJ
|
refs: EMPTY_OBJ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let currentInstance: ComponentInstance | null = null
|
export let currentInstance: ComponentInstance | null = null
|
||||||
|
|
||||||
export function setupStatefulComponent(
|
export function setupStatefulComponent(instance: ComponentInstance) {
|
||||||
instance: ComponentInstance,
|
|
||||||
props: Data | null
|
|
||||||
) {
|
|
||||||
const Component = instance.type as ComponentOptions
|
const Component = instance.type as ComponentOptions
|
||||||
// 1. create render proxy
|
// 1. create render proxy
|
||||||
const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers))
|
const proxy = (instance.proxy = new Proxy(
|
||||||
// 2. resolve initial props
|
instance,
|
||||||
initializeProps(instance, Component.props, props)
|
RenderProxyHandlers
|
||||||
// 3. call setup()
|
) as any)
|
||||||
|
// 2. call setup()
|
||||||
if (Component.setup) {
|
if (Component.setup) {
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
instance.bindings = Component.setup.call(proxy, proxy)
|
// TODO should pass reactive props here
|
||||||
|
instance.state = Component.setup.call(proxy, instance.props)
|
||||||
currentInstance = null
|
currentInstance = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderComponentRoot(
|
export function renderComponentRoot(instance: ComponentInstance): VNode {
|
||||||
instance: ComponentInstance,
|
const { type: Component, proxy } = instance
|
||||||
useAlreadyResolvedProps?: boolean
|
if (isFunction(Component)) {
|
||||||
): VNode {
|
return normalizeVNode(Component(instance))
|
||||||
const { type, vnode, proxy, bindings, $slots } = instance
|
|
||||||
const renderArg: RenderFunctionArg = {
|
|
||||||
state: bindings || EMPTY_OBJ,
|
|
||||||
slots: $slots,
|
|
||||||
props: null as any,
|
|
||||||
attrs: null as any
|
|
||||||
}
|
|
||||||
if (useAlreadyResolvedProps) {
|
|
||||||
// initial render for stateful components with setup()
|
|
||||||
// props are already resolved
|
|
||||||
renderArg.props = instance.$props
|
|
||||||
renderArg.attrs = instance.$attrs
|
|
||||||
} else {
|
} else {
|
||||||
const { 0: props, 1: attrs } = resolveProps(
|
if (__DEV__ && !Component.render) {
|
||||||
(vnode as VNode).props,
|
|
||||||
type.props
|
|
||||||
)
|
|
||||||
instance.$props = renderArg.props = props
|
|
||||||
instance.$attrs = renderArg.attrs = attrs
|
|
||||||
}
|
|
||||||
if (isFunction(type)) {
|
|
||||||
return normalizeVNode(type(renderArg))
|
|
||||||
} else {
|
|
||||||
if (__DEV__ && !type.render) {
|
|
||||||
// TODO warn missing render
|
// TODO warn missing render
|
||||||
}
|
}
|
||||||
return normalizeVNode((type.render as Function).call(proxy, renderArg))
|
return normalizeVNode((Component.render as Function).call(proxy, instance))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,20 +43,6 @@ type NormalizedPropsOptions = Record<string, NormalizedProp>
|
|||||||
|
|
||||||
const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$'
|
const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$'
|
||||||
|
|
||||||
export function initializeProps(
|
|
||||||
instance: ComponentInstance,
|
|
||||||
options: ComponentPropsOptions | undefined,
|
|
||||||
rawProps: Data | null
|
|
||||||
) {
|
|
||||||
const { 0: props, 1: attrs } = resolveProps(rawProps, options)
|
|
||||||
instance.$props = __DEV__ ? immutable(props) : props
|
|
||||||
instance.$attrs = options
|
|
||||||
? __DEV__
|
|
||||||
? immutable(attrs)
|
|
||||||
: attrs
|
|
||||||
: instance.$props
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
@ -65,16 +51,15 @@ export function initializeProps(
|
|||||||
// - if has declared props: put declared ones in `props`, the rest in `attrs`
|
// - if has declared props: put declared ones in `props`, the rest in `attrs`
|
||||||
// - else: everything goes in `props`.
|
// - else: everything goes in `props`.
|
||||||
|
|
||||||
const EMPTY_PROPS = [EMPTY_OBJ, EMPTY_OBJ] as [Data, Data]
|
|
||||||
|
|
||||||
export function resolveProps(
|
export function resolveProps(
|
||||||
|
instance: ComponentInstance,
|
||||||
rawProps: any,
|
rawProps: any,
|
||||||
_options: ComponentPropsOptions | void
|
_options: ComponentPropsOptions | void
|
||||||
): [Data, Data] {
|
) {
|
||||||
const hasDeclaredProps = _options != null
|
const hasDeclaredProps = _options != null
|
||||||
const options = normalizePropsOptions(_options) as NormalizedPropsOptions
|
const options = normalizePropsOptions(_options) as NormalizedPropsOptions
|
||||||
if (!rawProps && !hasDeclaredProps) {
|
if (!rawProps && !hasDeclaredProps) {
|
||||||
return EMPTY_PROPS
|
return
|
||||||
}
|
}
|
||||||
const props: any = {}
|
const props: any = {}
|
||||||
let attrs: any = void 0
|
let attrs: any = void 0
|
||||||
@ -126,7 +111,13 @@ export function resolveProps(
|
|||||||
// if component has no declared props, $attrs === $props
|
// if component has no declared props, $attrs === $props
|
||||||
attrs = props
|
attrs = props
|
||||||
}
|
}
|
||||||
return [props, attrs]
|
|
||||||
|
instance.props = __DEV__ ? immutable(props) : props
|
||||||
|
instance.attrs = options
|
||||||
|
? __DEV__
|
||||||
|
? immutable(attrs)
|
||||||
|
: attrs
|
||||||
|
: instance.props
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizationMap = new WeakMap()
|
const normalizationMap = new WeakMap()
|
||||||
|
@ -18,6 +18,7 @@ import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
|
|||||||
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||||
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
|
||||||
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
import { effect, stop, ReactiveEffectOptions } from '@vue/observer'
|
||||||
|
import { resolveProps } from './componentProps'
|
||||||
|
|
||||||
const prodEffectOptions = {
|
const prodEffectOptions = {
|
||||||
scheduler: queueJob
|
scheduler: queueJob
|
||||||
@ -367,18 +368,16 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
const instance: ComponentInstance = (vnode.component = createComponentInstance(
|
const instance: ComponentInstance = (vnode.component = createComponentInstance(
|
||||||
Component
|
Component
|
||||||
))
|
))
|
||||||
const needsSetup = typeof Component === 'object' && Component.setup
|
|
||||||
if (needsSetup) {
|
|
||||||
setupStatefulComponent(instance, vnode.props)
|
|
||||||
}
|
|
||||||
instance.update = effect(() => {
|
instance.update = effect(() => {
|
||||||
if (!instance.vnode) {
|
if (!instance.vnode) {
|
||||||
// initial mount
|
// initial mount
|
||||||
instance.vnode = vnode
|
instance.vnode = vnode
|
||||||
const subTree = (instance.subTree = renderComponentRoot(
|
resolveProps(instance, vnode.props, Component.props)
|
||||||
instance,
|
// setup stateful
|
||||||
needsSetup
|
if (typeof Component === 'object' && Component.setup) {
|
||||||
))
|
setupStatefulComponent(instance)
|
||||||
|
}
|
||||||
|
const subTree = (instance.subTree = renderComponentRoot(instance))
|
||||||
// beforeMount hook
|
// beforeMount hook
|
||||||
if (instance.bm !== null) {
|
if (instance.bm !== null) {
|
||||||
invokeHooks(instance.bm)
|
invokeHooks(instance.bm)
|
||||||
@ -396,6 +395,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
next.component = instance
|
next.component = instance
|
||||||
instance.vnode = next
|
instance.vnode = next
|
||||||
instance.next = null
|
instance.next = null
|
||||||
|
resolveProps(instance, next.props, Component.props)
|
||||||
}
|
}
|
||||||
const prevTree = instance.subTree
|
const prevTree = instance.subTree
|
||||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||||
|
Loading…
Reference in New Issue
Block a user