Skip to content

Commit

Permalink
fix(types): should unwrap refs in collections when reading objects
Browse files Browse the repository at this point in the history
  • Loading branch information
JensDll committed Dec 15, 2021
1 parent 44b9527 commit c0cb8bf
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 5 deletions.
3 changes: 3 additions & 0 deletions packages/reactivity/src/index.ts
Expand Up @@ -13,6 +13,9 @@ export {
ToRefs,
UnwrapRef,
ShallowRef,
UnwrappedMap,
UnwrappedWeakMap,
UnwrappedSet,
ShallowUnwrapRef,
RefUnwrapBailTypes,
CustomRefFactory
Expand Down
34 changes: 30 additions & 4 deletions packages/reactivity/src/ref.ts
Expand Up @@ -66,9 +66,6 @@ export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}

export function ref<T extends object>(
value: T
): [T] extends [Ref] ? T : Ref<UnwrapRef<T>>
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
Expand Down Expand Up @@ -287,13 +284,42 @@ export type UnwrapRef<T> = T extends ShallowRef<infer V>
? UnwrapRefSimple<V>
: UnwrapRefSimple<T>

export interface UnwrappedMap<K, V>
extends Omit<Map<K, UnwrapRefSimple<V>>, 'set'> {
set(key: K, value: V): this
}

export interface UnwrappedWeakMap<K extends object, V>
extends Omit<WeakMap<K, UnwrapRefSimple<V>>, 'set'> {
set(key: K, value: V): this
}

export interface UnwrappedSet<T>
extends Omit<Set<UnwrapRefSimple<T>>, 'add' | 'has' | 'delete'> {
add(value: T): this
has(value: T): boolean
delete(value: T): boolean
}

type UnwrapRefCollectionTypes<T extends CollectionTypes> = T extends Map<
infer TMapKey,
infer TMap
>
? UnwrappedMap<TMapKey, TMap>
: T extends WeakMap<infer TWeakMapKey, infer TWeakMap>
? UnwrappedWeakMap<TWeakMapKey, TWeakMap>
: T extends Set<infer TSet>
? UnwrappedSet<TSet>
: T

export type UnwrapRefSimple<T> = T extends
| Function
| CollectionTypes
| BaseTypes
| Ref
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
? T
: T extends CollectionTypes
? UnwrapRefCollectionTypes<T>
: T extends Array<any>
? { [K in keyof T]: UnwrapRefSimple<T[K]> }
: T extends object & { [ShallowReactiveMarker]?: never }
Expand Down
3 changes: 3 additions & 0 deletions packages/runtime-core/src/index.ts
Expand Up @@ -153,6 +153,9 @@ export {
ToRefs,
UnwrapRef,
ShallowRef,
UnwrappedMap,
UnwrappedWeakMap,
UnwrappedSet,
ShallowUnwrapRef,
CustomRefFactory,
ReactiveFlags,
Expand Down
95 changes: 95 additions & 0 deletions test-dts/ref.test-d.ts
Expand Up @@ -70,6 +70,101 @@ function plainType(arg: number | Ref<number>) {
// should still unwrap in objects nested in arrays
const arr2 = ref([{ a: ref(1) }]).value
expectType<number>(arr2[0].a)

// unwrapped collections should all extend the native ones
// and be assignable if no unwrapping has happened
{
const someSet: Set<Ref<number>> = new Set()
const someWeakSet: WeakSet<Ref<number>> = new WeakSet()
const someMap: Map<string, Ref<number>> = new Map()
const someWeakMap: WeakMap<object, Ref<number>> = new WeakMap()
const setRef = ref(someSet)
const weakSetRef = ref(someWeakSet)
const mapRef = ref(someMap)
const weakMapRef = ref(someWeakMap)

expectType<Set<Ref<number>>>(setRef.value)
expectType<WeakSet<Ref<number>>>(weakSetRef.value)
expectType<Map<string, Ref<number>>>(mapRef.value)
expectType<WeakMap<object, Ref<number>>>(weakMapRef.value)
}

// should still unwrap in objects nested in collections
{
let someSet: Set<{ a: Ref<number> }> = new Set()
const setRef = ref(someSet)

const obj = {
a: ref(1)
}

// do not require unwrapped object
// on the `add`, `has`, and `delete` method
setRef.value.add(obj)
setRef.value.has(obj)
setRef.value.delete(obj)

// make sure to unwrap the object when reading
for (const { a } of setRef.value.values()) {
expectType<number>(a)
}

// @ts-expect-error should not be assignable back
// because `a` was unwrapped
someSet = setRef.value
}

{
let someWeakSet: WeakSet<{ a: Ref<number> }> = new WeakSet()
const weakSetRef = ref(someWeakSet)

const obj = {
a: ref(1)
}

weakSetRef.value.add(obj)
weakSetRef.value.has(obj)
weakSetRef.value.delete(obj)

// should be assignable back because WeakSets are not unwrapped
// (their values can never be read and only be used for lookup)
someWeakSet = weakSetRef.value
}

{
let someMap: Map<string, { a: Ref<number> }> = new Map()
const mapRef = ref(someMap)

const obj = {
a: ref(1)
}

// do not require unwrapped object on the `set` method
mapRef.value.set('key', obj)

// make sure to unwrap the object when reading
expectType<{ a: number } | undefined>(mapRef.value.get('key'))

// @ts-expect-error
someMap = mapRef.value
}

{
let someWeakMap: WeakMap<{}, { a: Ref<number> }> = new Map()
const weakMapRef = ref(someWeakMap)

const key = {}
const obj = {
a: ref(1)
}

weakMapRef.value.set(key, obj)

expectType<{ a: number } | undefined>(weakMapRef.value.get(key))

// @ts-expect-error
someWeakMap = weakMapRef.value
}
}

plainType(1)
Expand Down
1 change: 1 addition & 0 deletions test-dts/tsconfig.build.json
Expand Up @@ -5,6 +5,7 @@
"module": "esnext",
"strict": true,
"moduleResolution": "node",
"downlevelIteration": true,
"lib": ["esnext", "dom"]
},
"include": ["./*"]
Expand Down
3 changes: 2 additions & 1 deletion test-dts/tsconfig.json
Expand Up @@ -2,7 +2,8 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"declaration": true
"declaration": true,
"downlevelIteration": true
},
"exclude": ["../packages/*/__tests__", "../packages/template-explorer"]
}

0 comments on commit c0cb8bf

Please sign in to comment.