Przeglądaj źródła

fix(types): fix ref unwrapping type inference for nested shallowReactive & shallowRef

fix #4771
Evan You 4 lat temu
rodzic
commit
20a361541c

+ 1 - 0
packages/reactivity/src/index.ts

@@ -27,6 +27,7 @@ export {
   toRaw,
   ReactiveFlags,
   DeepReadonly,
+  ShallowReactive,
   UnwrapNestedRefs
 } from './reactive'
 export {

+ 7 - 1
packages/reactivity/src/reactive.ts

@@ -99,12 +99,18 @@ export function reactive(target: object) {
   )
 }
 
+export declare const ShallowReactiveMarker: unique symbol
+
+export type ShallowReactive<T> = 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<T extends object>(target: T): T {
+export function shallowReactive<T extends object>(
+  target: T
+): ShallowReactive<T> {
   return createReactiveObject(
     target,
     false,

+ 19 - 6
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<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
+
 export function shallowRef<T extends object>(
   value: T
-): T extends Ref ? T : Ref<T>
-export function shallowRef<T>(value: T): Ref<T>
-export function shallowRef<T = any>(): Ref<T | undefined>
+): T extends Ref ? T : ShallowRef<T>
+export function shallowRef<T>(value: T): ShallowRef<T>
+export function shallowRef<T = any>(): ShallowRef<T | undefined>
 export function shallowRef(value?: unknown) {
   return createRef(value, true)
 }
@@ -215,6 +225,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
 }
 
 export type ToRef<T> = [T] extends [Ref] ? T : Ref<T>
+
 export function toRef<T extends object, K extends keyof T>(
   object: T,
   key: K
@@ -258,7 +269,9 @@ export type ShallowUnwrapRef<T> = {
     : T[K]
 }
 
-export type UnwrapRef<T> = T extends Ref<infer V>
+export type UnwrapRef<T> = T extends ShallowRef<infer V>
+  ? V
+  : T extends Ref<infer V>
   ? UnwrapRefSimple<V>
   : UnwrapRefSimple<T>
 
@@ -271,7 +284,7 @@ export type UnwrapRefSimple<T> = T extends
   ? T
   : T extends Array<any>
   ? { [K in keyof T]: UnwrapRefSimple<T[K]> }
-  : T extends object
+  : T extends object & { [ShallowReactiveMarker]?: never }
   ? {
       [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>
     }

+ 2 - 1
packages/runtime-core/src/index.ts

@@ -161,7 +161,8 @@ export {
   UnwrapRef,
   ShallowUnwrapRef,
   WritableComputedOptions,
-  DeepReadonly
+  DeepReadonly,
+  ShallowReactive
 } from '@vue/reactivity'
 export {
   WatchEffect,

+ 59 - 7
test-dts/ref.test-d.ts

@@ -239,13 +239,65 @@ function testUnrefGenerics<T>(p: T | Ref<T>) {
 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<Ref<number>>(foo.value.bar)
+  expectType<number>(foo.value.bar.value)
+})
+
+// #4771
+describe('shallow reactive in reactive', () => {
+  const baz = reactive({
+    foo: shallowReactive({
+      a: {
+        b: ref(42)
+      }
+    })
+  })
+
+  const foo = toRef(baz, 'foo')
+
+  expectType<Ref<number>>(foo.value.a.b)
+  expectType<number>(foo.value.a.b.value)
 })
 
-const foo = toRef(baz, 'foo')
+describe('shallow ref in reactive', () => {
+  const x = reactive({
+    foo: shallowRef({
+      bar: {
+        baz: ref(123),
+        qux: reactive({
+          z: ref(123)
+        })
+      }
+    })
+  })
+
+  expectType<Ref<number>>(x.foo.bar.baz)
+  expectType<number>(x.foo.bar.qux.z)
+})
+
+describe('ref in shallow ref', () => {
+  const x = shallowRef({
+    a: ref(123)
+  })
 
-expectType<Ref<number>>(foo.value.bar)
-expectType<number>(foo.value.bar.value)
+  expectType<Ref<number>>(x.value.a)
+})
+
+describe('reactive in shallow ref', () => {
+  const x = shallowRef({
+    a: reactive({
+      b: ref(0)
+    })
+  })
+
+  expectType<number>(x.value.a.b)
+})