From 333ceaa4b5e2d4eb4b3a1cc15de2cf21b3ad667c Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 30 May 2019 23:16:15 +0800 Subject: [PATCH] wip: props proxy for setup() --- packages/runtime-core/src/component.ts | 34 +++++++++++++++------ packages/runtime-core/src/componentProps.ts | 30 +++++++++++++++--- packages/runtime-core/src/createRenderer.ts | 2 +- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 6f61660e..af5af229 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -1,5 +1,10 @@ import { VNode, normalizeVNode, VNodeChild } from './vnode' -import { ReactiveEffect, UnwrapValue, observable } from '@vue/observer' +import { + ReactiveEffect, + UnwrapValue, + observable, + immutable +} from '@vue/observer' import { isFunction, EMPTY_OBJ } from '@vue/shared' import { RenderProxyHandlers } from './componentProxy' import { ComponentPropsOptions, PropValidator } from './componentProps' @@ -79,7 +84,8 @@ export type ComponentInstance

= { update: ReactiveEffect effects: ReactiveEffect[] | null // the rest are only for stateful components - proxy: ComponentPublicProperties | null + renderProxy: ComponentPublicProperties | null + propsProxy: Data | null state: S props: P attrs: Data @@ -109,7 +115,8 @@ export function createComponentInstance(type: any): ComponentInstance { next: null, subTree: null as any, update: null as any, - proxy: null, + renderProxy: null, + propsProxy: null, bm: null, m: null, @@ -138,28 +145,37 @@ export let currentInstance: ComponentInstance | null = null export function setupStatefulComponent(instance: ComponentInstance) { const Component = instance.type as ComponentOptions // 1. create render proxy - const proxy = (instance.proxy = new Proxy( + const proxy = (instance.renderProxy = new Proxy( instance, RenderProxyHandlers ) as any) // 2. call setup() - if (Component.setup) { + const { setup } = Component + if (setup) { currentInstance = instance - // TODO should pass reactive props here - instance.state = observable(Component.setup.call(proxy, instance.props)) + // 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 + ? immutable(instance.props) + : null) + instance.state = observable(setup.call(proxy, propsProxy)) currentInstance = null } } export function renderComponentRoot(instance: ComponentInstance): VNode { - const { type: Component, proxy } = instance + const { type: Component, renderProxy } = instance if (isFunction(Component)) { return normalizeVNode(Component(instance)) } else { if (__DEV__ && !Component.render) { // TODO warn missing render } - return normalizeVNode((Component.render as Function).call(proxy, instance)) + return normalizeVNode( + (Component.render as Function).call(renderProxy, instance) + ) } } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 3b013540..e2aad8da 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -1,4 +1,4 @@ -import { immutable, unwrap } from '@vue/observer' +import { immutable, unwrap, lock, unlock } from '@vue/observer' import { EMPTY_OBJ, camelize, @@ -61,8 +61,25 @@ export function resolveProps( if (!rawProps && !hasDeclaredProps) { return } + const props: any = {} let attrs: any = void 0 + + // update the instance propsProxy (passed to setup()) to trigger potential + // changes + const propsProxy = instance.propsProxy + const setProp = propsProxy + ? (key: string, val: any) => { + props[key] = val + propsProxy[key] = val + } + : (key: string, val: any) => { + props[key] = val + } + + // allow mutation of propsProxy (which is immutable by default) + unlock() + if (rawProps != null) { for (const key in rawProps) { // key, ref, slots are reserved @@ -74,7 +91,7 @@ export function resolveProps( if (hasDeclaredProps && !options.hasOwnProperty(key)) { ;(attrs || (attrs = {}))[key] = rawProps[key] } else { - props[key] = rawProps[key] + setProp(key, rawProps[key]) } } } @@ -89,17 +106,17 @@ export function resolveProps( // default values if (hasDefault && currentValue === undefined) { const defaultValue = opt.default - props[key] = isFunction(defaultValue) ? defaultValue() : defaultValue + setProp(key, isFunction(defaultValue) ? defaultValue() : defaultValue) } // boolean casting if (opt[BooleanFlags.shouldCast]) { if (isAbsent && !hasDefault) { - props[key] = false + setProp(key, false) } else if ( opt[BooleanFlags.shouldCastTrue] && (currentValue === '' || currentValue === hyphenate(key)) ) { - props[key] = true + setProp(key, true) } } // runtime validation @@ -112,6 +129,9 @@ export function resolveProps( attrs = props } + // lock immutable + lock() + instance.props = __DEV__ ? immutable(props) : props instance.attrs = options ? __DEV__ diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 60505a42..77a8ea39 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -442,7 +442,7 @@ export function createRenderer(options: RendererOptions) { instance.vnode = vnode resolveProps(instance, vnode.props, Component.props) // setup stateful - if (typeof Component === 'object' && Component.setup) { + if (typeof Component === 'object') { setupStatefulComponent(instance) } const subTree = (instance.subTree = renderComponentRoot(instance))