diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index b2a980fc..a1728939 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -1,5 +1,5 @@ import { VNode, normalizeVNode, VNodeChild } from './vnode' -import { ReactiveEffect } from '@vue/observer' +import { ReactiveEffect, observable } from '@vue/observer' import { isFunction, EMPTY_OBJ } from '@vue/shared' import { RenderProxyHandlers } from './componentProxy' import { ComponentPropsOptions, PropValidator } from './componentProps' @@ -30,6 +30,9 @@ export interface ComponentPublicProperties

{ // TODO $refs: Data $slots: Data + + $root: ComponentInstance | null + $parent: ComponentInstance | null } export interface ComponentOptions< @@ -147,7 +150,7 @@ export function setupStatefulComponent(instance: ComponentInstance) { if (Component.setup) { currentInstance = instance // TODO should pass reactive props here - instance.state = Component.setup.call(proxy, instance.props) + instance.state = observable(Component.setup.call(proxy, instance.props)) currentInstance = null } } diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index a447b61d..0974e6cb 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -1 +1,55 @@ -export const RenderProxyHandlers = {} +import { ComponentInstance } from './component' +import { isObservable, unwrap } from '@vue/observer' + +// TODO use proper implementation +function isValue(binding: any) { + return isObservable(binding) && unwrap(binding).hasOwnProperty('value') +} + +export const RenderProxyHandlers = { + get(target: ComponentInstance, key: string) { + const { state, props } = target + if (state.hasOwnProperty(key)) { + const value = state[key] + return isValue(value) ? value.value : value + } else if (props.hasOwnProperty(key)) { + return props[key] + } else { + switch (key) { + case '$state': + return target.state + case '$props': + return target.props + case '$attrs': + return target.attrs + case '$slots': + return target.slots + case '$refs': + return target.refs + default: + break + } + } + }, + set(target: ComponentInstance, key: string, value: any): boolean { + const { state } = target + if (state.hasOwnProperty(key)) { + const binding = state[key] + if (isValue(binding)) { + binding.value = value + } else { + state[key] = value + } + return true + } else { + if (__DEV__) { + if (key[0] === '$') { + // TODO warn attempt of mutating public property + } else if (target.props.hasOwnProperty(key)) { + // TODO warn attempt of mutating prop + } + } + } + return false + } +} diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index 52d0c1df..2f433290 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -389,13 +389,16 @@ export function createRenderer(options: RendererOptions) { queuePostFlushCb(instance.m) } } else { - // this is triggered by processComponent with `next` already set + // component update + // This is triggered by mutation of component's own state (next: null) + // OR parent calling processComponent (next: VNode) const { next } = instance if (next != null) { next.component = instance instance.vnode = next instance.next = null resolveProps(instance, next.props, Component.props) + // TODO slots } const prevTree = instance.subTree const nextTree = (instance.subTree = renderComponentRoot(instance)) @@ -738,8 +741,14 @@ export function createRenderer(options: RendererOptions) { : getNextHostNode(vnode.component.subTree) } - return function render(vnode: VNode, dom: HostNode): VNode { - patch(dom._vnode, vnode, dom) + return function render(vnode: VNode | null, dom: HostNode): VNode | null { + if (vnode == null) { + if (dom._vnode) { + unmount(dom._vnode, true) + } + } else { + patch(dom._vnode, vnode, dom) + } flushPostFlushCbs() return (dom._vnode = vnode) }