diff --git a/packages/reactivity/src/ref.ts b/packages/reactivity/src/ref.ts index 8338ffab2..d2898673e 100644 --- a/packages/reactivity/src/ref.ts +++ b/packages/reactivity/src/ref.ts @@ -128,16 +128,44 @@ export function toRef( // RelativePath extends object -> true type BaseTypes = string | number | boolean | Node | Window -// Recursively unwraps nested value bindings. -export type UnwrapRef = { - cRef: T extends ComputedRef ? UnwrapRef : T - ref: T extends Ref ? UnwrapRef : T - array: T - object: { [K in keyof T]: UnwrapRef } -}[T extends ComputedRef - ? 'cRef' - : T extends Array - ? '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 extends ComputedRef + ? UnwrapRefSimple + : T extends Ref ? UnwrapRefSimple : UnwrapRefSimple + +type UnwrapRefSimple = T extends Function | CollectionTypes | BaseTypes | Ref + ? T + : T extends Array ? T : T extends object ? UnwrappedObject : 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 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 = { [P in keyof T]: UnwrapRef } & SymbolExtract diff --git a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts index fc4c895ec..acf4f5b37 100644 --- a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts +++ b/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 + const refKey = ref('foo') const Comp = { setup() { diff --git a/test-dts/ref.test-d.ts b/test-dts/ref.test-d.ts index 4f862a068..f9b72312b 100644 --- a/test-dts/ref.test-d.ts +++ b/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) { // ref coercing @@ -20,6 +20,16 @@ function plainType(arg: number | Ref) { }) expectType>(nestedRef) expectType<{ foo: number }>(nestedRef.value) + + // tuple + expectType<[number, string]>(unref(ref([1, '1']))) + + interface IteratorFoo { + [Symbol.iterator]: any + } + + // with symbol + expectType(unref(ref(null))) } plainType(1)