From 6b10f0cd1da942c1d96746672b5f595df7d125b5 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 27 Jan 2020 15:15:22 -0500 Subject: [PATCH] refactor: remove implicit reactive() call on renderContext 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 | 15 ++++++++++----- packages/runtime-core/src/component.ts | 6 +++--- packages/runtime-core/src/componentProxy.ts | 16 ++++++++++++---- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/packages/runtime-core/src/apiOptions.ts b/packages/runtime-core/src/apiOptions.ts index 9eb3009b..7216edaf 100644 --- a/packages/runtime-core/src/apiOptions.ts +++ b/packages/runtime-core/src/apiOptions.ts @@ -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++) { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index ff3533bd..70971260 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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 = {} } } diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts index 1adad4aa..77fb117e 100644 --- a/packages/runtime-core/src/componentProxy.ts +++ b/packages/runtime-core/src/componentProxy.ts @@ -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 = { get(target: ComponentInternalInstance, key: string) { // fast path for unscopables when using `with` block @@ -102,7 +104,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler = { 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 = { 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 = { 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(