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`.
This commit is contained in:
parent
763faac182
commit
6b10f0cd1d
@ -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++) {
|
||||
|
@ -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 = {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)) {
|
||||
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(
|
||||
|
Loading…
Reference in New Issue
Block a user