Skip to content

Commit e67f655

Browse files
committed
refactor(runtime-core): revert setup() result reactive conversion
BREAKING CHANGE: revert setup() result reactive conversion Revert 6b10f0c & a840e7d. The motivation of the original change was avoiding unnecessary deep conversions, but that can be achieved by explicitly marking values non-reactive via `markNonReactive`. Removing the reactive conversion behavior leads to an usability issue in that plain objects containing refs (which is what most composition functions will return), when exposed as a nested property from `setup()`, will not unwrap the refs in templates. This goes against the "no .value in template" intuition and the only workaround requires users to manually wrap it again with `reactive()`. So in this commit we are reverting to the previous behavior where objects returned from `setup()` are implicitly wrapped with `reactive()` for deep ref unwrapping.
1 parent 11d2fb2 commit e67f655

File tree

3 files changed

+11
-46
lines changed

3 files changed

+11
-46
lines changed

packages/runtime-core/src/component.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { VNode, VNodeChild, isVNode } from './vnode'
22
import {
3+
reactive,
34
ReactiveEffect,
45
shallowReadonly,
56
pauseTracking,
@@ -398,7 +399,7 @@ export function handleSetupResult(
398399
}
399400
// setup returned bindings.
400401
// assuming a render function compiled from template is present.
401-
instance.renderContext = setupResult
402+
instance.renderContext = reactive(setupResult)
402403
} else if (__DEV__ && setupResult !== undefined) {
403404
warn(
404405
`setup() should return an object. Received: ${
@@ -474,10 +475,6 @@ function finishComponentSetup(
474475
currentInstance = null
475476
currentSuspense = null
476477
}
477-
478-
if (instance.renderContext === EMPTY_OBJ) {
479-
instance.renderContext = {}
480-
}
481478
}
482479

483480
// used to identify a setup context proxy

packages/runtime-core/src/componentProxy.ts

+6-31
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,7 @@ import {
88
ComputedOptions,
99
MethodOptions
1010
} from './apiOptions'
11-
import {
12-
ReactiveEffect,
13-
isRef,
14-
isReactive,
15-
Ref,
16-
ComputedRef,
17-
unref
18-
} from '@vue/reactivity'
11+
import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
1912
import { warn } from './warning'
2013
import { Slots } from './componentSlots'
2114
import {
@@ -48,17 +41,11 @@ export type ComponentPublicInstance<
4841
$nextTick: typeof nextTick
4942
$watch: typeof instanceWatch
5043
} & P &
51-
UnwrapSetupBindings<B> &
44+
UnwrapRef<B> &
5245
D &
5346
ExtractComputedReturns<C> &
5447
M
5548

56-
type UnwrapSetupBindings<B> = { [K in keyof B]: UnwrapBinding<B[K]> }
57-
58-
type UnwrapBinding<B> = B extends ComputedRef<any>
59-
? B extends ComputedRef<infer V> ? V : B
60-
: B extends Ref<infer V> ? V : B
61-
6249
const publicPropertiesMap: Record<
6350
string,
6451
(i: ComponentInternalInstance) => any
@@ -115,17 +102,17 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
115102
case AccessTypes.DATA:
116103
return data[key]
117104
case AccessTypes.CONTEXT:
118-
return unref(renderContext[key])
105+
return renderContext[key]
119106
case AccessTypes.PROPS:
120107
return propsProxy![key]
121108
// default: just fallthrough
122109
}
123110
} else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
124111
accessCache![key] = AccessTypes.DATA
125112
return data[key]
126-
} else if (hasOwn(renderContext, key)) {
113+
} else if (renderContext !== EMPTY_OBJ && hasOwn(renderContext, key)) {
127114
accessCache![key] = AccessTypes.CONTEXT
128-
return unref(renderContext[key])
115+
return renderContext[key]
129116
} else if (type.props != null) {
130117
// only cache other properties when instance has declared (this stable)
131118
// props
@@ -180,19 +167,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
180167
if (data !== EMPTY_OBJ && hasOwn(data, key)) {
181168
data[key] = value
182169
} else if (hasOwn(renderContext, key)) {
183-
// context is already reactive (user returned reactive object from setup())
184-
// just set directly
185-
if (isReactive(renderContext)) {
186-
renderContext[key] = value
187-
} else {
188-
// handle potential ref set
189-
const oldValue = renderContext[key]
190-
if (isRef(oldValue) && !isRef(value)) {
191-
oldValue.value = value
192-
} else {
193-
renderContext[key] = value
194-
}
195-
}
170+
renderContext[key] = value
196171
} else if (key[0] === '$' && key.slice(1) in target) {
197172
__DEV__ &&
198173
warn(

test-dts/defineComponent.test-d.tsx

+3-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
defineComponent,
55
PropType,
66
ref,
7-
Ref,
87
reactive,
98
createApp
109
} from './index'
@@ -65,15 +64,12 @@ describe('with object props', () => {
6564
// setup context
6665
return {
6766
c: ref(1),
68-
d: reactive({
67+
d: {
6968
e: ref('hi')
70-
}),
69+
},
7170
f: reactive({
7271
g: ref('hello' as GT)
73-
}),
74-
h: {
75-
i: ref('hi')
76-
}
72+
})
7773
}
7874
},
7975
render() {
@@ -106,9 +102,6 @@ describe('with object props', () => {
106102
expectType<string>(this.d.e)
107103
expectType<GT>(this.f.g)
108104

109-
// should not unwrap refs nested under non-reactive objects
110-
expectType<Ref<string>>(this.h.i)
111-
112105
// setup context properties should be mutable
113106
this.c = 2
114107

0 commit comments

Comments
 (0)