Zapic's Blog
ref<T>() / ref(T) / ref() as Ref<T>? Vue3中的Ref类型体操

Ref的四种写法

这是一个ref:

const refA = ref();

什么都没有, 所以是一个Ref<any>
可以这样:

refA.value = 123;
refA.value = "123";
refA.value = ["123", 123];

这是另一个ref:

const refB = ref(1);

这时候, 他就是一个Ref<number>, 所以:

refB.value = 2;
refB.value = -1;
refB.value = {}; // Error: Type '{}' is not assignable to type 'number'

这是第三个ref:

const refC = ref() as Ref<number>;

这时候, 因为as了, 所以他是一个Ref<number>, 性质跟refB类似, 除了一点:
他并不安全.
refC声明的时候并没有初始化值, 其类型本应该是Ref<any>, 或者详细点说是Ref<any | undefined>(any包括undefined, 所以合并成了any)
所以可能会导致意外的类型错误, 慎用as.

这是第四个ref:

const refD = ref<number>();

通过泛型, 限定了Ref的类型只能为number, 但是由于未被初始化, 所以实际类型是Ref<number | undefined>
所以使用前必须判空, 安全起来了.

热身运动做完了, 接下来该做点体操了.

0x00:

const refE = ref([] as number[]);
const refF = ref([]);

结果是Ref<number[]>Ref<never[]>.
因此:

refE.push(1);
refF.push(1); // Error

0x01:

const refG = ref(ref(ref(1)));
const refH = ref(reactive({name: ""}));

结果是Ref<number>Ref<{ name: string }>, ref会对深层Ref解套.

0x02:

interface A {
  name: string;
  age?: number;
}
const refI = ref({ name: "Zapic" }) as Ref<A>;
const refJ = ref<A>({ name: "Zapic" });
const refK = ref({ name: "Zapic" });

结果是Ref<A>/Ref<A>Ref<{ name: string }>
因此:

refI.age = 24;
refJ.age = 24;
refK.age = 24; // Error, age not exist on { name: string }

ref的类型声明

export declare function ref<T extends object>(value: T): ToRef<T>;

declare type ToRef<T> = [T] extends [Ref] ? T : Ref<UnwrapRef<T>>;

对于对象, 会直接调用reactive变成响应式对象, 其属性也会被监控.
无论嵌套多少层, 最终结果仅会有一层Ref包装.

export declare function ref<T>(value: T): Ref<UnwrapRef<T>>;

对于在定义时初始化的ref, 其类型直接变为Ref<T>, 同时保证仅有一层Ref包装.

export declare function ref<T = any>(): Ref<T | undefined>;

对于在定义时未初始化值的ref, 其类型默认为any. 若通过泛型参数指定了类型, 其最终类型也会包括一个undefined.

UnwrapRef的类型声明

declare type BaseTypes = string | number | boolean;
declare type CollectionTypes = IterableCollections | WeakCollections;

export declare type UnwrapRef<T> = T extends Ref<infer V> ? UnwrapRefSimple<V> : UnwrapRefSimple<T>;

declare type UnwrapRefSimple<T> = T extends Function | CollectionTypes | BaseTypes | Ref | RefUnwrapBailTypes[keyof RefUnwrapBailTypes] ? T : T extends Array<any> ? {
    [K in keyof T]: UnwrapRefSimple<T[K]>;
} : T extends object ? {
    [P in keyof T]: P extends symbol ? T[P] : UnwrapRef<T[P]>;
} : T;

若传入UnwrapRef的类型为Ref<V>, 则提取出Ref<V>的实际类型(V), 传入UnwrapRefSimple处理, 否则传入类型本身.
UnwrapRefSimple<T>内:

  1. TFunction CollectionTypes BaseTypes Ref RefUnwrapBailTypes时返回其本身.
    其中RefUnwrapBailTypes是一个提供给外部插件的特殊接口, 用于指定哪些类型应当被跳过.
    解Ref包裹的过程已经在Ref<infer V>实现, 如果再次遇到Ref, 可能是对象内部属性的Ref, 不应当被处理.
  2. TArray 类型时, 其内部的所有元素都会被丢进UnwrapRefSimple再次处理.
  3. TObject 类型时, 如果其内部某属性键类型为 Symbol, 则直接原样返回, 否则丢进UnwrapRef内再次处理.