From eac8a4baa3d3034d18d46bd3bb6c89c4ce56dba0 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 29 May 2019 10:43:27 +0800 Subject: [PATCH] wip: props immutability --- packages/runtime-core/src/component.ts | 110 +++++++++++--------- packages/runtime-core/src/componentProps.ts | 2 +- packages/runtime-core/src/componentProxy.ts | 1 + packages/runtime-core/src/createRenderer.ts | 23 ++-- 4 files changed, 81 insertions(+), 55 deletions(-) create mode 100644 packages/runtime-core/src/componentProxy.ts diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 21d2e07d1..b7a1ea3e2 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -1,26 +1,32 @@ import { VNode, normalizeVNode, VNodeChild } from './vnode' import { ReactiveEffect } from '@vue/observer' import { isFunction, EMPTY_OBJ } from '@vue/shared' -import { resolveProps, ComponentPropsOptions } from './componentProps' +import { RenderProxyHandlers } from './componentProxy' +import { + resolveProps, + ComponentPropsOptions, + initializeProps, + PropValidator +} from './componentProps' interface Value { value: T } +export type Data = { [key: string]: any } + type UnwrapBindings = { [key in keyof T]: T[key] extends Value ? V : T[key] } -type Prop = { (): T } | { new (...args: any[]): T & object } - type ExtractPropTypes = { - readonly [key in keyof PropOptions]: PropOptions[key] extends Prop + readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator< + infer V + > ? V : PropOptions[key] extends null | undefined ? any : PropOptions[key] } -export type Data = { [key: string]: any } - export interface ComponentPublicProperties

{ $state: S $props: P @@ -64,21 +70,6 @@ export type Slots = Readonly<{ [name: string]: Slot }> -// no-op, for type inference only -export function createComponent< - RawProps, - RawBindings, - Props = ExtractPropTypes, - Bindings = UnwrapBindings ->( - options: ComponentOptions -): { - // for TSX - new (): { $props: Props } -} { - return options as any -} - type LifecycleHook = Function[] | null export interface LifecycleHooks { @@ -97,22 +88,38 @@ export interface LifecycleHooks { export type ComponentInstance = { type: FunctionalComponent | ComponentOptions - vnode: VNode | null + vnode: VNode next: VNode | null - subTree: VNode | null + subTree: VNode update: ReactiveEffect + + // the rest are only for stateful components bindings: Data | null proxy: Data | null } & LifecycleHooks & ComponentPublicProperties -export function createComponentInstance(vnode: VNode): ComponentInstance { - const type = vnode.type as any - const instance = { +// no-op, for type inference only +export function createComponent< + RawProps, + RawBindings, + Props = ExtractPropTypes, + Bindings = UnwrapBindings +>( + options: ComponentOptions +): { + // for TSX + new (): { $props: Props } +} { + return options as any +} + +export function createComponentInstance(type: any): ComponentInstance { + return { type, - vnode: null, + vnode: null as any, next: null, - subTree: null, + subTree: null as any, update: null as any, bindings: null, proxy: null, @@ -136,41 +143,50 @@ export function createComponentInstance(vnode: VNode): ComponentInstance { $slots: EMPTY_OBJ, $state: EMPTY_OBJ } - if (typeof type === 'object' && type.setup) { - setupStatefulComponent(instance) - } - return instance } export let currentInstance: ComponentInstance | null = null -const RenderProxyHandlers = {} - -export function setupStatefulComponent(instance: ComponentInstance) { +export function setupStatefulComponent( + instance: ComponentInstance, + props: Data | null +) { + 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 type = instance.type as ComponentOptions - if (type.setup) { + if (Component.setup) { currentInstance = instance - instance.bindings = type.setup.call(proxy, proxy) + instance.bindings = Component.setup.call(proxy, proxy) currentInstance = null } } -export function renderComponentRoot(instance: ComponentInstance): VNode { +export function renderComponentRoot( + instance: ComponentInstance, + useAlreadyResolvedProps?: boolean +): VNode { const { type, vnode, proxy, bindings, $slots } = instance - if (!type) debugger - const { 0: props, 1: attrs } = resolveProps( - (vnode as VNode).props, - type.props - ) - const renderArg = { + const renderArg: RenderFunctionArg = { state: bindings || EMPTY_OBJ, slots: $slots, - props, - attrs + 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 { + 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)) diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 8d39c7835..89a71e7ba 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -45,7 +45,7 @@ const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$' export function initializeProps( instance: ComponentInstance, - options: NormalizedPropsOptions | undefined, + options: ComponentPropsOptions | undefined, rawProps: Data | null ) { const { 0: props, 1: attrs } = resolveProps(rawProps, options) diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts new file mode 100644 index 000000000..a447b61d2 --- /dev/null +++ b/packages/runtime-core/src/componentProxy.ts @@ -0,0 +1 @@ +export const RenderProxyHandlers = {} diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index b379c4463..0b1d0cc1d 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -11,7 +11,8 @@ import { ComponentInstance, renderComponentRoot, shouldUpdateComponent, - createComponentInstance + createComponentInstance, + setupStatefulComponent } from './component' import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared' import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags' @@ -348,14 +349,22 @@ export function createRenderer(options: RendererOptions) { container: HostNode, anchor?: HostNode ) { + const Component = vnode.type const instance: ComponentInstance = (vnode.component = createComponentInstance( - vnode + Component )) + const needsSetup = typeof Component === 'object' && (Component as any).setup + if (needsSetup) { + setupStatefulComponent(instance, vnode.props) + } instance.update = effect(() => { if (!instance.vnode) { // initial mount instance.vnode = vnode - const subTree = (instance.subTree = renderComponentRoot(instance)) + const subTree = (instance.subTree = renderComponentRoot( + instance, + needsSetup + )) if (instance.bm !== null) { invokeHooks(instance.bm) } @@ -373,7 +382,7 @@ export function createRenderer(options: RendererOptions) { instance.vnode = next instance.next = null } - const prevTree = instance.subTree as VNode + const prevTree = instance.subTree const nextTree = (instance.subTree = renderComponentRoot(instance)) patch( prevTree, @@ -651,7 +660,7 @@ export function createRenderer(options: RendererOptions) { function move(vnode: VNode, container: HostNode, anchor: HostNode) { if (vnode.component != null) { - move(vnode.component.subTree as VNode, container, anchor) + move(vnode.component.subTree, container, anchor) return } if (vnode.type === Fragment) { @@ -671,7 +680,7 @@ export function createRenderer(options: RendererOptions) { if (instance != null) { // TODO teardown component stop(instance.update) - unmount(instance.subTree as VNode, doRemove) + unmount(instance.subTree, doRemove) if (instance.um !== null) { queuePostFlushCb(instance.um) } @@ -702,7 +711,7 @@ export function createRenderer(options: RendererOptions) { function getNextHostNode(vnode: VNode): HostNode { return vnode.component === null ? hostNextSibling(vnode.anchor || vnode.el) - : getNextHostNode(vnode.component.subTree as VNode) + : getNextHostNode(vnode.component.subTree) } return function render(vnode: VNode, dom: HostNode): VNode {