浏览代码

types: improve UnwrapRef (#579)

Evan You 6 年之前
父节点
当前提交
e33291bd0e
共有 3 个文件被更改,包括 53 次插入16 次删除
  1. 41 13
      packages/reactivity/src/ref.ts
  2. 1 2
      packages/runtime-core/__tests__/apiTemplateRef.spec.ts
  3. 11 1
      test-dts/ref.test-d.ts

+ 41 - 13
packages/reactivity/src/ref.ts

@@ -128,16 +128,44 @@ export function toRef<T extends object, K extends keyof T>(
 // RelativePath extends object -> true
 type BaseTypes = string | number | boolean | Node | Window
 
-// Recursively unwraps nested value bindings.
-export type UnwrapRef<T> = {
-  cRef: T extends ComputedRef<infer V> ? UnwrapRef<V> : T
-  ref: T extends Ref<infer V> ? UnwrapRef<V> : T
-  array: T
-  object: { [K in keyof T]: UnwrapRef<T[K]> }
-}[T extends ComputedRef<any>
-  ? 'cRef'
-  : T extends Array<any>
-    ? 'array'
-    : T extends Ref | Function | CollectionTypes | BaseTypes
-      ? 'ref' // bail out on types that shouldn't be unwrapped
-      : T extends object ? 'object' : 'ref']
+export type UnwrapRef<T> = T extends ComputedRef<infer V>
+  ? UnwrapRefSimple<V>
+  : T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>
+
+type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref
+  ? T
+  : T extends Array<any> ? T : T extends object ? UnwrappedObject<T> : T
+
+// Extract all known symbols from an object
+// when unwrapping Object the symbols are not `in keyof`, this should cover all the
+// known symbols
+type SymbolExtract<T> = (T extends { [Symbol.asyncIterator]: infer V }
+  ? { [Symbol.asyncIterator]: V }
+  : {}) &
+  (T extends { [Symbol.hasInstance]: infer V }
+    ? { [Symbol.hasInstance]: V }
+    : {}) &
+  (T extends { [Symbol.isConcatSpreadable]: infer V }
+    ? { [Symbol.isConcatSpreadable]: V }
+    : {}) &
+  (T extends { [Symbol.iterator]: infer V } ? { [Symbol.iterator]: V } : {}) &
+  (T extends { [Symbol.match]: infer V } ? { [Symbol.match]: V } : {}) &
+  (T extends { [Symbol.matchAll]: infer V } ? { [Symbol.matchAll]: V } : {}) &
+  (T extends { [Symbol.observable]: infer V }
+    ? { [Symbol.observable]: V }
+    : {}) &
+  (T extends { [Symbol.replace]: infer V } ? { [Symbol.replace]: V } : {}) &
+  (T extends { [Symbol.search]: infer V } ? { [Symbol.search]: V } : {}) &
+  (T extends { [Symbol.species]: infer V } ? { [Symbol.species]: V } : {}) &
+  (T extends { [Symbol.split]: infer V } ? { [Symbol.split]: V } : {}) &
+  (T extends { [Symbol.toPrimitive]: infer V }
+    ? { [Symbol.toPrimitive]: V }
+    : {}) &
+  (T extends { [Symbol.toStringTag]: infer V }
+    ? { [Symbol.toStringTag]: V }
+    : {}) &
+  (T extends { [Symbol.unscopables]: infer V }
+    ? { [Symbol.unscopables]: V }
+    : {})
+
+type UnwrappedObject<T> = { [P in keyof T]: UnwrapRef<T[P]> } & SymbolExtract<T>

+ 1 - 2
packages/runtime-core/__tests__/apiTemplateRef.spec.ts

@@ -4,7 +4,6 @@ import {
   h,
   render,
   nextTick,
-  Ref,
   defineComponent,
   reactive
 } from '@vue/runtime-test'
@@ -143,7 +142,7 @@ describe('api: template refs', () => {
       foo: ref(null),
       bar: ref(null)
     }
-    const refKey = ref('foo') as Ref<keyof typeof refs>
+    const refKey = ref<keyof typeof refs>('foo')
 
     const Comp = {
       setup() {

+ 11 - 1
test-dts/ref.test-d.ts

@@ -1,5 +1,5 @@
 import { expectType } from 'tsd'
-import { Ref, ref, isRef, unref } from './index'
+import { Ref, ref, isRef, unref, UnwrapRef } from './index'
 
 function plainType(arg: number | Ref<number>) {
   // ref coercing
@@ -20,6 +20,16 @@ function plainType(arg: number | Ref<number>) {
   })
   expectType<Ref<{ foo: number }>>(nestedRef)
   expectType<{ foo: number }>(nestedRef.value)
+
+  // tuple
+  expectType<[number, string]>(unref(ref([1, '1'])))
+
+  interface IteratorFoo {
+    [Symbol.iterator]: any
+  }
+
+  // with symbol
+  expectType<IteratorFoo | null>(unref(ref<IteratorFoo | null>(null)))
 }
 
 plainType(1)