泛型约束与高级推导
核心问题解析
问题一:Vue 的 ref 为什么能自动解包?
在 Vue 3 中:
import { ref } from 'vue';
const count = ref(1); // Ref<number>
const user = ref({ name: 'Alice' }); // Ref<{ name: string }>
// 在模板或 computed 中可以直接使用:
count.value; // number
user.name; // ❌ 错误!应该是 user.value.name但更神奇的是深层 ref 解包(自动 unwrap):
const state = ref({
count: ref(1),
name: 'Bob'
});
// 使用时:
state.value.count; // ✅ number,不是 Ref<number>!答案:Vue 使用了条件类型 + 递归 + 分布式条件类型,在类型层面实现了“如果是一个 ref,则解包其值类型”。
问题二:React 的 useState 怎么推导初始值类型?
const [count, setCount] = useState(0); // count: number
const [name, setName] = useState('hello'); // name: string
const [user, setUser] = useState<User | null>(null); // user: User | nulluseState 如何知道 0 是 number,而 null 是 User | null?
答案:泛型参数从实参推导(Type Inference),并结合联合类型处理 null 等特殊情况。
问题三:如何实现类型级别的“逻辑判断”?
我们希望在类型系统中实现类似 if-else 的逻辑:
// 伪代码
if T is array:
return item type
else if T is function:
return return type
else:
return T答案:使用条件类型(T extends U ? X : Y)和 infer 进行类型提取。
学习目标详解
目标一:掌握 infer 在类型提取中的应用
infer 用于在 extends 条件中声明一个待推断的类型变量。
1. 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type T1 = ReturnType<() => string>; // string
type T2 = ReturnType<(x: number) => number>; // number2. 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never;
type T3 = ElementType<number[]>; // number
type T4 = ElementType<string[][]>; // string[]3. 提取 Promise 解包类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
type T5 = Awaited<Promise<string>>; // string
type T6 = Awaited<number>; // numberAwaited 实际是递归的,能处理 Promise<Promise<T>>。
目标二:实现类型级别的“模式匹配”
通过组合 extends、infer 和联合类型,实现类型分支逻辑。
示例:自动解包 Ref 类型(模拟 Vue Ref)
// 假设 Ref 定义如下
interface Ref<T> {
value: T;
}
// 类型级别的“模式匹配”:如果是 Ref<T>,则返回 T,否则保持原类型
type UnwrapRef<T> = T extends Ref<infer U> ? U : T;
type T7 = UnwrapRef<Ref<string>>; // string
type T8 = UnwrapRef<number>; // number深层解包(递归 + 分布式)
// 更复杂的 UnwrapRef,支持对象属性中的 Ref 自动解包
type DeepUnwrapRef<T> = T extends Ref<infer U>
? DeepUnwrapRef<U> // 如果是 Ref,递归解包其值
: T extends object
? { [K in keyof T]: DeepUnwrapRef<T[K]> } // 如果是对象,递归解包每个属性
: T; // 基本类型直接返回
// 测试
interface State {
count: Ref<number>;
profile: {
name: string;
age: Ref<number>;
};
}
type Unwrapped = DeepUnwrapRef<State>;
// {
// count: number;
// profile: { name: string; age: number };
// }这就是 Vue 3 unref、shallowUnwrap 等工具类型的实现原理。
目标三:理解分布式条件类型与延迟推导
1. 分布式条件类型(Distributive Conditional Types)
当 T 是联合类型时,T extends U ? X : Y 会自动分发到每个成员:
type ToArray<T> = T extends any ? T[] : never;
type T9 = ToArray<string | number>;
// 等价于:
// (string extends any ? string[] : never) | (number extends any ? number[] : never)
// string[] | number[]注意:只有“裸类型参数”(naked type parameter)才会分布式。
2. 非分布式(包裹后不分发)
type ToArrayWrapped<T> = [T] extends [any] ? T[] : never;
type T10 = ToArrayWrapped<string | number>;
// [string | number] extends [any] → true → (string | number)[]
// 结果:(string | number)[]3. 延迟推导(Defer Inference)
有时我们希望推迟 infer 的解析,直到所有信息都可用。
// 用于等待类型完全解析
type Deferred<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;在复杂递归类型中,延迟推导可避免过早求值。
目标四:分析主流框架中的泛型设计
1. Vue 3:Ref<T> 与 ComputedRef
// 简化版 Ref 定义
interface Ref<T> {
value: T;
}
// 自动解包逻辑(简化)
type UnwrapRefSimple<T> = T extends Ref<infer U> ? U : T;
// 在 reactive 对象中自动解包
declare function reactive<T>(obj: T): {
[K in keyof T]: UnwrapRefSimple<T[K]>
};2. React:useState<T>(initial: T): [T, SetState<T>]
function useState<S>(initialState: S | (() => S)): [S, (s: S | ((prev: S) => S)) => void];
// TS 能从 initialState 推导出 S
const [count, setCount] = useState(0); // S 推导为 number3. Redux Toolkit:createSelector
// 多个输入 selector,一个结果 selector
const selectTotal = createSelector(
[selectItems, selectTax],
(items, tax) => items.reduce(/*...*/) * (1 + tax)
);
// 类型系统能推导 items 和 tax 的类型,并检查返回值使用了泛型元组和映射类型来保证类型安全。
总结:本节核心要点
| 技术 | 用途 | 示例 |
|---|---|---|
infer | 提取类型片段 | infer R 提取返回值 |
| 条件类型 | 类型分支(if-else) | T extends U ? X : Y |
| 分布式条件类型 | 联合类型自动分发 | string | number → string[] | number[] |
| 递归类型 | 处理嵌套结构 | DeepUnwrapRef |
| 模式匹配 | 类型级别逻辑判断 | 模拟 Vue Ref 解包 |
高级类型 = 函数式编程 in 类型系统
你正在用 extends、infer、? : 等操作符,在编译期“计算”出新的类型。
练习题
练习题 1:实现 Parameters<T>
提取函数参数类型为元组:
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type T1 = Parameters<(a: string, b: number) => void>;
// [string, number]练习题 2:实现 NonNullable<T>
去除 null 和 undefined:
type NonNullable<T> = T extends null | undefined ? never : T;
type T2 = NonNullable<string \| null \| undefined>; // string练习题 3:模拟 Vue 的 unref
实现一个类型,如果是 Ref<T> 则返回 T,否则返回原类型:
interface Ref<T> {
value: T;
}
type UnwrapRef<T> = T extends Ref<infer U> ? U : T;
type T3 = UnwrapRef<Ref<number>>; // number
type T4 = UnwrapRef<string>; // string练习题 4:分布式条件类型
解释以下类型的结果:
type Box<T> = T extends string ? `boxed-${T}` : { wrapped: T };
type T5 = Box<'a' | 'b' | number>;
// 分布式:
// 'a' → `boxed-a`
// 'b' → `boxed-b`
// number → { wrapped: number }
// 结果:`boxed-a` | `boxed-b` | { wrapped: number }练习题 5:深层解包对象中的 Ref
扩展 UnwrapRef,使其能解包对象属性中的 Ref:
type DeepUnwrapRef<T> = T extends Ref<infer U>
? DeepUnwrapRef<U>
: T extends object
? { [K in keyof T]: DeepUnwrapRef<T[K]> }
: T;
interface State {
name: Ref<string>;
age: number;
meta: {
tags: Ref<string[]>;
};
}
type T6 = DeepUnwrapRef<State>;
// {
// name: string;
// age: number;
// meta: { tags: string[] }
// }通过本节学习,你已经掌握了 TypeScript 类型系统的“元编程”能力。infer、条件类型、分布式、递归等特性,让你能在类型层面实现复杂的逻辑判断和结构转换,这正是 Vue、React、Redux 等框架类型安全的基石。