]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
refactor: remove implicit reactive() call on renderContext
authorEvan You <yyx990803@gmail.com>
Mon, 27 Jan 2020 20:15:22 +0000 (15:15 -0500)
committerEvan You <yyx990803@gmail.com>
Mon, 27 Jan 2020 21:00:18 +0000 (16:00 -0500)
reference: https://github.com/vuejs/rfcs/issues/121

BREAKING CHANGE: object returned from `setup()` are no longer implicitly
passed to `reactive()`.

  The renderContext is the object returned by `setup()` (or a new object
  if no setup() is present). Before this change, it was implicitly passed
  to `reactive()` for ref unwrapping. But this has the side effect of
  unnecessary deep reactive conversion on properties that should not be
  made reactive (e.g. computed return values and injected non-reactive
  objects), and can lead to performance issues.

  This change removes the `reactive()` call and instead performs a
  shallow ref unwrapping at the render proxy level. The breaking part is
  when the user returns an object with a plain property from `setup()`,
  e.g. `return { count: 0 }`, this property will no longer trigger
  updates when mutated by a in-template event handler. Instead, explicit
  refs are required.

  This also means that any objects not explicitly made reactive in
  `setup()` will remain non-reactive. This can be desirable when
  exposing heavy external stateful objects on `this`.

packages/runtime-core/src/apiOptions.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProxy.ts

index 9eb3009b2553f9ae9b82bf7e548f32b13a51e3cf..7216edafb7c3fbf8b1432988066c3e2d3a180509 100644 (file)
@@ -216,10 +216,6 @@ export function applyOptions(
   options: ComponentOptions,
   asMixin: boolean = false
 ) {
-  const renderContext =
-    instance.renderContext === EMPTY_OBJ
-      ? (instance.renderContext = __SSR__ ? {} : reactive({}))
-      : instance.renderContext
   const ctx = instance.proxy!
   const {
     // composition
@@ -250,6 +246,11 @@ export function applyOptions(
     errorCaptured
   } = options
 
+  const renderContext =
+    instance.renderContext === EMPTY_OBJ
+      ? (instance.renderContext = {})
+      : instance.renderContext
+
   const globalMixins = instance.appContext.mixins
   // call it only during dev
   const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
@@ -285,12 +286,13 @@ export function applyOptions(
           checkDuplicateProperties!(OptionTypes.DATA, key)
         }
       }
-      instance.data = __SSR__ ? data : reactive(data)
+      instance.data = reactive(data)
     } else {
       // existing data: this is a mixin or extends.
       extend(instance.data, data)
     }
   }
+
   if (computedOptions) {
     for (const key in computedOptions) {
       const opt = (computedOptions as ComputedOptions)[key]
@@ -335,11 +337,13 @@ export function applyOptions(
       }
     }
   }
+
   if (watchOptions) {
     for (const key in watchOptions) {
       createWatcher(watchOptions[key], renderContext, ctx, key)
     }
   }
+
   if (provideOptions) {
     const provides = isFunction(provideOptions)
       ? provideOptions.call(ctx)
@@ -348,6 +352,7 @@ export function applyOptions(
       provide(key, provides[key])
     }
   }
+
   if (injectOptions) {
     if (isArray(injectOptions)) {
       for (let i = 0; i < injectOptions.length; i++) {
index ff3533bd55fbfec7b58aef60552383198ed9cf73..70971260f5257cc576a9ad10eba6a6d15ad172c4 100644 (file)
@@ -1,5 +1,5 @@
 import { VNode, VNodeChild, isVNode } from './vnode'
-import { ReactiveEffect, reactive, shallowReadonly } from '@vue/reactivity'
+import { ReactiveEffect, shallowReadonly } from '@vue/reactivity'
 import {
   PublicInstanceProxyHandlers,
   ComponentPublicInstance,
@@ -375,7 +375,7 @@ export function handleSetupResult(
     }
     // setup returned bindings.
     // assuming a render function compiled from template is present.
-    instance.renderContext = __SSR__ ? setupResult : reactive(setupResult)
+    instance.renderContext = setupResult
   } else if (__DEV__ && setupResult !== undefined) {
     warn(
       `setup() should return an object. Received: ${
@@ -453,7 +453,7 @@ function finishComponentSetup(
   }
 
   if (instance.renderContext === EMPTY_OBJ) {
-    instance.renderContext = __SSR__ ? {} : reactive({})
+    instance.renderContext = {}
   }
 }
 
index 1adad4aa056a149a511f47b6b5e297a68bd5b5e1..77fb117e89973deee08d089b57149d99b18f07ce 100644 (file)
@@ -8,7 +8,7 @@ import {
   ComputedOptions,
   MethodOptions
 } from './apiOptions'
-import { UnwrapRef, ReactiveEffect } from '@vue/reactivity'
+import { UnwrapRef, ReactiveEffect, isRef, toRaw } from '@vue/reactivity'
 import { warn } from './warning'
 import { Slots } from './componentSlots'
 import {
@@ -73,6 +73,8 @@ const enum AccessTypes {
   OTHER
 }
 
+const unwrapRef = (val: unknown) => (isRef(val) ? val.value : val)
+
 export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
   get(target: ComponentInternalInstance, key: string) {
     // fast path for unscopables when using `with` block
@@ -102,7 +104,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
           case AccessTypes.DATA:
             return data[key]
           case AccessTypes.CONTEXT:
-            return renderContext[key]
+            return unwrapRef(renderContext[key])
           case AccessTypes.PROPS:
             return propsProxy![key]
           // default: just fallthrough
@@ -112,7 +114,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
         return data[key]
       } else if (hasOwn(renderContext, key)) {
         accessCache![key] = AccessTypes.CONTEXT
-        return renderContext[key]
+        return unwrapRef(renderContext[key])
       } else if (type.props != null) {
         // only cache other properties when instance has declared (this stable)
         // props
@@ -167,7 +169,13 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
     if (data !== EMPTY_OBJ && hasOwn(data, key)) {
       data[key] = value
     } else if (hasOwn(renderContext, key)) {
-      renderContext[key] = value
+      const oldValue = renderContext[key]
+      value = toRaw(value)
+      if (isRef(oldValue) && !isRef(value)) {
+        oldValue.value = value
+      } else {
+        renderContext[key] = value
+      }
     } else if (key[0] === '$' && key.slice(1) in target) {
       __DEV__ &&
         warn(