Skip to content

Commit 6b10f0c

Browse files
committed
refactor: remove implicit reactive() call on renderContext
reference: vuejs/rfcs#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`.
1 parent 763faac commit 6b10f0c

File tree

3 files changed

+25
-12
lines changed

3 files changed

+25
-12
lines changed

packages/runtime-core/src/apiOptions.ts

+10-5
Original file line numberDiff line numberDiff line change
@@ -216,10 +216,6 @@ export function applyOptions(
216216
options: ComponentOptions,
217217
asMixin: boolean = false
218218
) {
219-
const renderContext =
220-
instance.renderContext === EMPTY_OBJ
221-
? (instance.renderContext = __SSR__ ? {} : reactive({}))
222-
: instance.renderContext
223219
const ctx = instance.proxy!
224220
const {
225221
// composition
@@ -250,6 +246,11 @@ export function applyOptions(
250246
errorCaptured
251247
} = options
252248

249+
const renderContext =
250+
instance.renderContext === EMPTY_OBJ
251+
? (instance.renderContext = {})
252+
: instance.renderContext
253+
253254
const globalMixins = instance.appContext.mixins
254255
// call it only during dev
255256
const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
@@ -285,12 +286,13 @@ export function applyOptions(
285286
checkDuplicateProperties!(OptionTypes.DATA, key)
286287
}
287288
}
288-
instance.data = __SSR__ ? data : reactive(data)
289+
instance.data = reactive(data)
289290
} else {
290291
// existing data: this is a mixin or extends.
291292
extend(instance.data, data)
292293
}
293294
}
295+
294296
if (computedOptions) {
295297
for (const key in computedOptions) {
296298
const opt = (computedOptions as ComputedOptions)[key]
@@ -335,11 +337,13 @@ export function applyOptions(
335337
}
336338
}
337339
}
340+
338341
if (watchOptions) {
339342
for (const key in watchOptions) {
340343
createWatcher(watchOptions[key], renderContext, ctx, key)
341344
}
342345
}
346+
343347
if (provideOptions) {
344348
const provides = isFunction(provideOptions)
345349
? provideOptions.call(ctx)
@@ -348,6 +352,7 @@ export function applyOptions(
348352
provide(key, provides[key])
349353
}
350354
}
355+
351356
if (injectOptions) {
352357
if (isArray(injectOptions)) {
353358
for (let i = 0; i < injectOptions.length; i++) {

packages/runtime-core/src/component.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { VNode, VNodeChild, isVNode } from './vnode'
2-
import { ReactiveEffect, reactive, shallowReadonly } from '@vue/reactivity'
2+
import { ReactiveEffect, shallowReadonly } from '@vue/reactivity'
33
import {
44
PublicInstanceProxyHandlers,
55
ComponentPublicInstance,
@@ -375,7 +375,7 @@ export function handleSetupResult(
375375
}
376376
// setup returned bindings.
377377
// assuming a render function compiled from template is present.
378-
instance.renderContext = __SSR__ ? setupResult : reactive(setupResult)
378+
instance.renderContext = setupResult
379379
} else if (__DEV__ && setupResult !== undefined) {
380380
warn(
381381
`setup() should return an object. Received: ${
@@ -453,7 +453,7 @@ function finishComponentSetup(
453453
}
454454

455455
if (instance.renderContext === EMPTY_OBJ) {
456-
instance.renderContext = __SSR__ ? {} : reactive({})
456+
instance.renderContext = {}
457457
}
458458
}
459459

packages/runtime-core/src/componentProxy.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
ComputedOptions,
99
MethodOptions
1010
} from './apiOptions'
11-
import { UnwrapRef, ReactiveEffect } from '@vue/reactivity'
11+
import { UnwrapRef, ReactiveEffect, isRef, toRaw } from '@vue/reactivity'
1212
import { warn } from './warning'
1313
import { Slots } from './componentSlots'
1414
import {
@@ -73,6 +73,8 @@ const enum AccessTypes {
7373
OTHER
7474
}
7575

76+
const unwrapRef = (val: unknown) => (isRef(val) ? val.value : val)
77+
7678
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
7779
get(target: ComponentInternalInstance, key: string) {
7880
// fast path for unscopables when using `with` block
@@ -102,7 +104,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
102104
case AccessTypes.DATA:
103105
return data[key]
104106
case AccessTypes.CONTEXT:
105-
return renderContext[key]
107+
return unwrapRef(renderContext[key])
106108
case AccessTypes.PROPS:
107109
return propsProxy![key]
108110
// default: just fallthrough
@@ -112,7 +114,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
112114
return data[key]
113115
} else if (hasOwn(renderContext, key)) {
114116
accessCache![key] = AccessTypes.CONTEXT
115-
return renderContext[key]
117+
return unwrapRef(renderContext[key])
116118
} else if (type.props != null) {
117119
// only cache other properties when instance has declared (this stable)
118120
// props
@@ -167,7 +169,13 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
167169
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
168170
data[key] = value
169171
} else if (hasOwn(renderContext, key)) {
170-
renderContext[key] = value
172+
const oldValue = renderContext[key]
173+
value = toRaw(value)
174+
if (isRef(oldValue) && !isRef(value)) {
175+
oldValue.value = value
176+
} else {
177+
renderContext[key] = value
178+
}
171179
} else if (key[0] === '$' && key.slice(1) in target) {
172180
__DEV__ &&
173181
warn(

0 commit comments

Comments
 (0)