From 6ceb7321140db494078bdff8147acbf4a99041b3 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 29 May 2019 11:36:16 +0800 Subject: [PATCH] refactor: component setup flow --- packages/runtime-core/src/component.ts | 106 +++++++------------- packages/runtime-core/src/componentProps.ts | 29 ++---- packages/runtime-core/src/createRenderer.ts | 16 +-- 3 files changed, 54 insertions(+), 97 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index b7a1ea3e..b2a980fc 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -2,12 +2,7 @@ import { VNode, normalizeVNode, VNodeChild } from './vnode' import { ReactiveEffect } from '@vue/observer' import { isFunction, EMPTY_OBJ } from '@vue/shared' import { RenderProxyHandlers } from './componentProxy' -import { - resolveProps, - ComponentPropsOptions, - initializeProps, - PropValidator -} from './componentProps' +import { ComponentPropsOptions, PropValidator } from './componentProps' interface Value { value: T @@ -37,13 +32,6 @@ export interface ComponentPublicProperties

{ $slots: Data } -interface RenderFunctionArg { - state: B - props: P - attrs: Data - slots: Slots -} - export interface ComponentOptions< RawProps = ComponentPropsOptions, RawBindings = Data | void, @@ -54,22 +42,16 @@ export interface ComponentOptions< setup?: (props: Props) => RawBindings render?: ( this: ComponentPublicProperties, - ctx: RenderFunctionArg + ctx: ComponentInstance ) => VNodeChild } export interface FunctionalComponent

{ - (ctx: RenderFunctionArg): any + (ctx: ComponentInstance

): any props?: ComponentPropsOptions

displayName?: string } -export type Slot = (...args: any[]) => VNode[] - -export type Slots = Readonly<{ - [name: string]: Slot -}> - type LifecycleHook = Function[] | null export interface LifecycleHooks { @@ -86,18 +68,26 @@ export interface LifecycleHooks { ec: LifecycleHook // errorCaptured } -export type ComponentInstance = { +export type Slot = (...args: any[]) => VNode[] + +export type Slots = Readonly<{ + [name: string]: Slot +}> + +export type ComponentInstance

= { type: FunctionalComponent | ComponentOptions vnode: VNode next: VNode | null subTree: VNode update: ReactiveEffect - // the rest are only for stateful components - bindings: Data | null - proxy: Data | null -} & LifecycleHooks & - ComponentPublicProperties + proxy: ComponentPublicProperties | null + state: S + props: P + attrs: Data + slots: Slots + refs: Data +} & LifecycleHooks // no-op, for type inference only export function createComponent< @@ -121,7 +111,6 @@ export function createComponentInstance(type: any): ComponentInstance { next: null, subTree: null as any, update: null as any, - bindings: null, proxy: null, bm: null, @@ -137,64 +126,41 @@ export function createComponentInstance(type: any): ComponentInstance { ec: null, // public properties - $attrs: EMPTY_OBJ, - $props: EMPTY_OBJ, - $refs: EMPTY_OBJ, - $slots: EMPTY_OBJ, - $state: EMPTY_OBJ + state: EMPTY_OBJ, + props: EMPTY_OBJ, + attrs: EMPTY_OBJ, + slots: EMPTY_OBJ, + refs: EMPTY_OBJ } } export let currentInstance: ComponentInstance | null = null -export function setupStatefulComponent( - instance: ComponentInstance, - props: Data | null -) { +export function setupStatefulComponent(instance: ComponentInstance) { const Component = instance.type as ComponentOptions // 1. create render proxy - const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers)) - // 2. resolve initial props - initializeProps(instance, Component.props, props) - // 3. call setup() + const proxy = (instance.proxy = new Proxy( + instance, + RenderProxyHandlers + ) as any) + // 2. call setup() if (Component.setup) { 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 } } -export function renderComponentRoot( - instance: ComponentInstance, - useAlreadyResolvedProps?: boolean -): VNode { - 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 +export function renderComponentRoot(instance: ComponentInstance): VNode { + const { type: Component, proxy } = instance + if (isFunction(Component)) { + return normalizeVNode(Component(instance)) } else { - const { 0: props, 1: attrs } = resolveProps( - (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) { + if (__DEV__ && !Component.render) { // TODO warn missing render } - return normalizeVNode((type.render as Function).call(proxy, renderArg)) + return normalizeVNode((Component.render as Function).call(proxy, instance)) } } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 89a71e7b..3b013540 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -43,20 +43,6 @@ type NormalizedPropsOptions = Record 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. // - filter out reserved keys (key, ref, slots) // - 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` // - else: everything goes in `props`. -const EMPTY_PROPS = [EMPTY_OBJ, EMPTY_OBJ] as [Data, Data] - export function resolveProps( + instance: ComponentInstance, rawProps: any, _options: ComponentPropsOptions | void -): [Data, Data] { +) { const hasDeclaredProps = _options != null const options = normalizePropsOptions(_options) as NormalizedPropsOptions if (!rawProps && !hasDeclaredProps) { - return EMPTY_PROPS + return } const props: any = {} let attrs: any = void 0 @@ -126,7 +111,13 @@ export function resolveProps( // if component has no declared 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() diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index ba3caa06..52d0c1df 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -18,6 +18,7 @@ import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared' import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags' import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler' import { effect, stop, ReactiveEffectOptions } from '@vue/observer' +import { resolveProps } from './componentProps' const prodEffectOptions = { scheduler: queueJob @@ -367,18 +368,16 @@ export function createRenderer(options: RendererOptions) { const instance: ComponentInstance = (vnode.component = createComponentInstance( Component )) - const needsSetup = typeof Component === 'object' && Component.setup - if (needsSetup) { - setupStatefulComponent(instance, vnode.props) - } instance.update = effect(() => { if (!instance.vnode) { // initial mount instance.vnode = vnode - const subTree = (instance.subTree = renderComponentRoot( - instance, - needsSetup - )) + resolveProps(instance, vnode.props, Component.props) + // setup stateful + if (typeof Component === 'object' && Component.setup) { + setupStatefulComponent(instance) + } + const subTree = (instance.subTree = renderComponentRoot(instance)) // beforeMount hook if (instance.bm !== null) { invokeHooks(instance.bm) @@ -396,6 +395,7 @@ export function createRenderer(options: RendererOptions) { next.component = instance instance.vnode = next instance.next = null + resolveProps(instance, next.props, Component.props) } const prevTree = instance.subTree const nextTree = (instance.subTree = renderComponentRoot(instance))