wip: props proxy for setup()

This commit is contained in:
Evan You 2019-05-30 23:16:15 +08:00
parent e665a133e9
commit 333ceaa4b5
3 changed files with 51 additions and 15 deletions

View File

@ -1,5 +1,10 @@
import { VNode, normalizeVNode, VNodeChild } from './vnode' 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 { isFunction, EMPTY_OBJ } from '@vue/shared'
import { RenderProxyHandlers } from './componentProxy' import { RenderProxyHandlers } from './componentProxy'
import { ComponentPropsOptions, PropValidator } from './componentProps' import { ComponentPropsOptions, PropValidator } from './componentProps'
@ -79,7 +84,8 @@ export type ComponentInstance<P = Data, S = Data> = {
update: ReactiveEffect update: ReactiveEffect
effects: ReactiveEffect[] | null effects: ReactiveEffect[] | null
// the rest are only for stateful components // the rest are only for stateful components
proxy: ComponentPublicProperties | null renderProxy: ComponentPublicProperties | null
propsProxy: Data | null
state: S state: S
props: P props: P
attrs: Data attrs: Data
@ -109,7 +115,8 @@ 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,
proxy: null, renderProxy: null,
propsProxy: null,
bm: null, bm: null,
m: null, m: null,
@ -138,28 +145,37 @@ export let currentInstance: ComponentInstance | null = null
export function setupStatefulComponent(instance: ComponentInstance) { export function setupStatefulComponent(instance: ComponentInstance) {
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( const proxy = (instance.renderProxy = new Proxy(
instance, instance,
RenderProxyHandlers RenderProxyHandlers
) as any) ) as any)
// 2. call setup() // 2. call setup()
if (Component.setup) { const { setup } = Component
if (setup) {
currentInstance = instance currentInstance = instance
// TODO should pass reactive props here // the props proxy makes the props object passed to setup() reactive
instance.state = observable(Component.setup.call(proxy, instance.props)) // 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 currentInstance = null
} }
} }
export function renderComponentRoot(instance: ComponentInstance): VNode { export function renderComponentRoot(instance: ComponentInstance): VNode {
const { type: Component, proxy } = instance const { type: Component, renderProxy } = instance
if (isFunction(Component)) { if (isFunction(Component)) {
return normalizeVNode(Component(instance)) return normalizeVNode(Component(instance))
} else { } else {
if (__DEV__ && !Component.render) { if (__DEV__ && !Component.render) {
// TODO warn missing render // TODO warn missing render
} }
return normalizeVNode((Component.render as Function).call(proxy, instance)) return normalizeVNode(
(Component.render as Function).call(renderProxy, instance)
)
} }
} }

View File

@ -1,4 +1,4 @@
import { immutable, unwrap } from '@vue/observer' import { immutable, unwrap, lock, unlock } from '@vue/observer'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
camelize, camelize,
@ -61,8 +61,25 @@ export function resolveProps(
if (!rawProps && !hasDeclaredProps) { if (!rawProps && !hasDeclaredProps) {
return return
} }
const props: any = {} const props: any = {}
let attrs: any = void 0 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) { if (rawProps != null) {
for (const key in rawProps) { for (const key in rawProps) {
// key, ref, slots are reserved // key, ref, slots are reserved
@ -74,7 +91,7 @@ export function resolveProps(
if (hasDeclaredProps && !options.hasOwnProperty(key)) { if (hasDeclaredProps && !options.hasOwnProperty(key)) {
;(attrs || (attrs = {}))[key] = rawProps[key] ;(attrs || (attrs = {}))[key] = rawProps[key]
} else { } else {
props[key] = rawProps[key] setProp(key, rawProps[key])
} }
} }
} }
@ -89,17 +106,17 @@ export function resolveProps(
// default values // default values
if (hasDefault && currentValue === undefined) { if (hasDefault && currentValue === undefined) {
const defaultValue = opt.default const defaultValue = opt.default
props[key] = isFunction(defaultValue) ? defaultValue() : defaultValue setProp(key, isFunction(defaultValue) ? defaultValue() : defaultValue)
} }
// boolean casting // boolean casting
if (opt[BooleanFlags.shouldCast]) { if (opt[BooleanFlags.shouldCast]) {
if (isAbsent && !hasDefault) { if (isAbsent && !hasDefault) {
props[key] = false setProp(key, false)
} else if ( } else if (
opt[BooleanFlags.shouldCastTrue] && opt[BooleanFlags.shouldCastTrue] &&
(currentValue === '' || currentValue === hyphenate(key)) (currentValue === '' || currentValue === hyphenate(key))
) { ) {
props[key] = true setProp(key, true)
} }
} }
// runtime validation // runtime validation
@ -112,6 +129,9 @@ export function resolveProps(
attrs = props attrs = props
} }
// lock immutable
lock()
instance.props = __DEV__ ? immutable(props) : props instance.props = __DEV__ ? immutable(props) : props
instance.attrs = options instance.attrs = options
? __DEV__ ? __DEV__

View File

@ -442,7 +442,7 @@ export function createRenderer(options: RendererOptions) {
instance.vnode = vnode instance.vnode = vnode
resolveProps(instance, vnode.props, Component.props) resolveProps(instance, vnode.props, Component.props)
// setup stateful // setup stateful
if (typeof Component === 'object' && Component.setup) { if (typeof Component === 'object') {
setupStatefulComponent(instance) setupStatefulComponent(instance)
} }
const subTree = (instance.subTree = renderComponentRoot(instance)) const subTree = (instance.subTree = renderComponentRoot(instance))