Ver código fonte

wip: props proxy for setup()

Evan You 7 anos atrás
pai
commit
333ceaa4b5

+ 25 - 9
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<P = Data, S = Data> = {
   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)
+    )
   }
 }
 

+ 25 - 5
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__

+ 1 - 1
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))