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'
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
next: null,
subTree: null as any,
update: null as any,
- proxy: null,
+ renderProxy: null,
+ propsProxy: null,
bm: null,
m: 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)
+ )
}
}
-import { immutable, unwrap } from '@vue/observer'
+import { immutable, unwrap, lock, unlock } from '@vue/observer'
import {
EMPTY_OBJ,
camelize,
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
if (hasDeclaredProps && !options.hasOwnProperty(key)) {
;(attrs || (attrs = {}))[key] = rawProps[key]
} else {
- props[key] = rawProps[key]
+ setProp(key, rawProps[key])
}
}
}
// 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
attrs = props
}
+ // lock immutable
+ lock()
+
instance.props = __DEV__ ? immutable(props) : props
instance.attrs = options
? __DEV__