From 20a361541cc5faffa82cbf3f2d49639a97b3b678 Mon Sep 17 00:00:00 2001 From: Evan You Date: Sat, 9 Oct 2021 17:51:09 -0400 Subject: [PATCH] fix(types): fix ref unwrapping type inference for nested shallowReactive & shallowRef fix #4771 --- packages/reactivity/src/index.ts | 1 + packages/reactivity/src/reactive.ts | 8 +++- packages/reactivity/src/ref.ts | 25 ++++++++--- packages/runtime-core/src/index.ts | 3 +- test-dts/ref.test-d.ts | 66 ++++++++++++++++++++++++++--- 5 files changed, 88 insertions(+), 15 deletions(-) diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index b15f4d80..d608e8b8 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -27,6 +27,7 @@ export { toRaw, ReactiveFlags, DeepReadonly, + ShallowReactive, UnwrapNestedRefs } from './reactive' export { diff --git a/packages/reactivity/src/reactive.ts b/packages/reactivity/src/reactive.ts index 9cce2ea0..b56cb362 100644 --- a/packages/reactivity/src/reactive.ts +++ b/packages/reactivity/src/reactive.ts @@ -99,12 +99,18 @@ export function reactive(target: object) { ) } +export declare const ShallowReactiveMarker: unique symbol + +export type ShallowReactive = T & { [ShallowReactiveMarker]?: true } + /** * Return a shallowly-reactive copy of the original object, where only the root * level properties are reactive. It also does not auto-unwrap refs (even at the * root level). */ -export function shallowReactive(target: T): T { +export function shallowReactive( + target: T +): ShallowReactive { return createReactiveObject( target, false, diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index f3337582..d1cc17ea 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -1,7 +1,13 @@ import { isTracking, trackEffects, triggerEffects } from './effect' import { TrackOpTypes, TriggerOpTypes } from './operations' import { isArray, hasChanged } from '@vue/shared' -import { isProxy, toRaw, isReactive, toReactive } from './reactive' +import { + isProxy, + toRaw, + isReactive, + toReactive, + ShallowReactiveMarker +} from './reactive' import { CollectionTypes } from './collectionHandlers' import { createDep, Dep } from './dep' @@ -74,11 +80,15 @@ export function ref(value?: unknown) { return createRef(value, false) } +declare const ShallowRefMarker: unique symbol + +type ShallowRef = Ref & { [ShallowRefMarker]?: true } + export function shallowRef( value: T -): T extends Ref ? T : Ref -export function shallowRef(value: T): Ref -export function shallowRef(): Ref +): T extends Ref ? T : ShallowRef +export function shallowRef(value: T): ShallowRef +export function shallowRef(): ShallowRef export function shallowRef(value?: unknown) { return createRef(value, true) } @@ -215,6 +225,7 @@ class ObjectRefImpl { } export type ToRef = [T] extends [Ref] ? T : Ref + export function toRef( object: T, key: K @@ -258,7 +269,9 @@ export type ShallowUnwrapRef = { : T[K] } -export type UnwrapRef = T extends Ref +export type UnwrapRef = T extends ShallowRef + ? V + : T extends Ref ? UnwrapRefSimple : UnwrapRefSimple @@ -271,7 +284,7 @@ export type UnwrapRefSimple = T extends ? T : T extends Array ? { [K in keyof T]: UnwrapRefSimple } - : T extends object + : T extends object & { [ShallowReactiveMarker]?: never } ? { [P in keyof T]: P extends symbol ? T[P] : UnwrapRef } diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index b1a59426..ff0aeb90 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -161,7 +161,8 @@ export { UnwrapRef, ShallowUnwrapRef, WritableComputedOptions, - DeepReadonly + DeepReadonly, + ShallowReactive } from '@vue/reactivity' export { WatchEffect, diff --git a/test-dts/ref.test-d.ts b/test-dts/ref.test-d.ts index 8129d618..d4462db9 100644 --- a/test-dts/ref.test-d.ts +++ b/test-dts/ref.test-d.ts @@ -239,13 +239,65 @@ function testUnrefGenerics(p: T | Ref) { testUnrefGenerics(1) // #4732 -const baz = shallowReactive({ - foo: { - bar: ref(42) - } +describe('ref in shallow reactive', () => { + const baz = shallowReactive({ + foo: { + bar: ref(42) + } + }) + + const foo = toRef(baz, 'foo') + + expectType>(foo.value.bar) + expectType(foo.value.bar.value) }) -const foo = toRef(baz, 'foo') +// #4771 +describe('shallow reactive in reactive', () => { + const baz = reactive({ + foo: shallowReactive({ + a: { + b: ref(42) + } + }) + }) -expectType>(foo.value.bar) -expectType(foo.value.bar.value) + const foo = toRef(baz, 'foo') + + expectType>(foo.value.a.b) + expectType(foo.value.a.b.value) +}) + +describe('shallow ref in reactive', () => { + const x = reactive({ + foo: shallowRef({ + bar: { + baz: ref(123), + qux: reactive({ + z: ref(123) + }) + } + }) + }) + + expectType>(x.foo.bar.baz) + expectType(x.foo.bar.qux.z) +}) + +describe('ref in shallow ref', () => { + const x = shallowRef({ + a: ref(123) + }) + + expectType>(x.value.a) +}) + +describe('reactive in shallow ref', () => { + const x = shallowRef({ + a: reactive({ + b: ref(0) + }) + }) + + expectType(x.value.a.b) +})