Skip to content

Commit 20a3615

Browse files
committed
fix(types): fix ref unwrapping type inference for nested shallowReactive & shallowRef
fix #4771
1 parent e772108 commit 20a3615

File tree

5 files changed

+88
-15
lines changed

5 files changed

+88
-15
lines changed

packages/reactivity/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export {
2727
toRaw,
2828
ReactiveFlags,
2929
DeepReadonly,
30+
ShallowReactive,
3031
UnwrapNestedRefs
3132
} from './reactive'
3233
export {

packages/reactivity/src/reactive.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,18 @@ export function reactive(target: object) {
9999
)
100100
}
101101

102+
export declare const ShallowReactiveMarker: unique symbol
103+
104+
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }
105+
102106
/**
103107
* Return a shallowly-reactive copy of the original object, where only the root
104108
* level properties are reactive. It also does not auto-unwrap refs (even at the
105109
* root level).
106110
*/
107-
export function shallowReactive<T extends object>(target: T): T {
111+
export function shallowReactive<T extends object>(
112+
target: T
113+
): ShallowReactive<T> {
108114
return createReactiveObject(
109115
target,
110116
false,

packages/reactivity/src/ref.ts

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { isTracking, trackEffects, triggerEffects } from './effect'
22
import { TrackOpTypes, TriggerOpTypes } from './operations'
33
import { isArray, hasChanged } from '@vue/shared'
4-
import { isProxy, toRaw, isReactive, toReactive } from './reactive'
4+
import {
5+
isProxy,
6+
toRaw,
7+
isReactive,
8+
toReactive,
9+
ShallowReactiveMarker
10+
} from './reactive'
511
import { CollectionTypes } from './collectionHandlers'
612
import { createDep, Dep } from './dep'
713

@@ -74,11 +80,15 @@ export function ref(value?: unknown) {
7480
return createRef(value, false)
7581
}
7682

83+
declare const ShallowRefMarker: unique symbol
84+
85+
type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
86+
7787
export function shallowRef<T extends object>(
7888
value: T
79-
): T extends Ref ? T : Ref<T>
80-
export function shallowRef<T>(value: T): Ref<T>
81-
export function shallowRef<T = any>(): Ref<T | undefined>
89+
): T extends Ref ? T : ShallowRef<T>
90+
export function shallowRef<T>(value: T): ShallowRef<T>
91+
export function shallowRef<T = any>(): ShallowRef<T | undefined>
8292
export function shallowRef(value?: unknown) {
8393
return createRef(value, true)
8494
}
@@ -215,6 +225,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
215225
}
216226

217227
export type ToRef<T> = [T] extends [Ref] ? T : Ref<T>
228+
218229
export function toRef<T extends object, K extends keyof T>(
219230
object: T,
220231
key: K
@@ -258,7 +269,9 @@ export type ShallowUnwrapRef<T> = {
258269
: T[K]
259270
}
260271

261-
export type UnwrapRef<T> = T extends Ref<infer V>
272+
export type UnwrapRef<T> = T extends ShallowRef<infer V>
273+
? V
274+
: T extends Ref<infer V>
262275
? UnwrapRefSimple<V>
263276
: UnwrapRefSimple<T>
264277

@@ -271,7 +284,7 @@ export type UnwrapRefSimple<T> = T extends
271284
? T
272285
: T extends Array<any>
273286
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
274-
: T extends object
287+
: T extends object & { [ShallowReactiveMarker]?: never }
275288
? {
276289
[P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
277290
}

packages/runtime-core/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ export {
161161
UnwrapRef,
162162
ShallowUnwrapRef,
163163
WritableComputedOptions,
164-
DeepReadonly
164+
DeepReadonly,
165+
ShallowReactive
165166
} from '@vue/reactivity'
166167
export {
167168
WatchEffect,

test-dts/ref.test-d.ts

+59-7
Original file line numberDiff line numberDiff line change
@@ -239,13 +239,65 @@ function testUnrefGenerics<T>(p: T | Ref<T>) {
239239
testUnrefGenerics(1)
240240

241241
// #4732
242-
const baz = shallowReactive({
243-
foo: {
244-
bar: ref(42)
245-
}
242+
describe('ref in shallow reactive', () => {
243+
const baz = shallowReactive({
244+
foo: {
245+
bar: ref(42)
246+
}
247+
})
248+
249+
const foo = toRef(baz, 'foo')
250+
251+
expectType<Ref<number>>(foo.value.bar)
252+
expectType<number>(foo.value.bar.value)
253+
})
254+
255+
// #4771
256+
describe('shallow reactive in reactive', () => {
257+
const baz = reactive({
258+
foo: shallowReactive({
259+
a: {
260+
b: ref(42)
261+
}
262+
})
263+
})
264+
265+
const foo = toRef(baz, 'foo')
266+
267+
expectType<Ref<number>>(foo.value.a.b)
268+
expectType<number>(foo.value.a.b.value)
246269
})
247270

248-
const foo = toRef(baz, 'foo')
271+
describe('shallow ref in reactive', () => {
272+
const x = reactive({
273+
foo: shallowRef({
274+
bar: {
275+
baz: ref(123),
276+
qux: reactive({
277+
z: ref(123)
278+
})
279+
}
280+
})
281+
})
282+
283+
expectType<Ref<number>>(x.foo.bar.baz)
284+
expectType<number>(x.foo.bar.qux.z)
285+
})
286+
287+
describe('ref in shallow ref', () => {
288+
const x = shallowRef({
289+
a: ref(123)
290+
})
249291

250-
expectType<Ref<number>>(foo.value.bar)
251-
expectType<number>(foo.value.bar.value)
292+
expectType<Ref<number>>(x.value.a)
293+
})
294+
295+
describe('reactive in shallow ref', () => {
296+
const x = shallowRef({
297+
a: reactive({
298+
b: ref(0)
299+
})
300+
})
301+
302+
expectType<number>(x.value.a.b)
303+
})

0 commit comments

Comments
 (0)