核心结论:何时用泛型约束?何时用条件类型?
泛型约束()用于限制泛型参数的类型范围,让你能安全地访问参数的特定属性;条件类型(T U ? X : Y)用于根据输入类型动态计算输出类型。两者都依赖 关键字,但作用完全不同。
一句话区分:
泛型约束:“这个泛型必须满足什么条件”(定义阶段限制)
条件类型:“如果这个类型满足条件,结果是什么类型”(计算阶段分支)
一、泛型约束( )
1.1 是什么?
泛型约束通过 关键字限定泛型参数必须包含某些属性或方法,从而让函数/类内部可以安全地使用这些成员。
1.2 基本语法
<T { : }>(arg: T): T {
.log(arg.); // ✅ 安全,因为T一定有属性
arg;
}
1.3 常见使用场景
| 场景 | 示例约束 | 作用 |
|——|———|——|
| 限制必须有属性 | T { : } | 处理字符串、数组等 |
| 限制必须为对象类型 | T | 排除原始类型 |
| 限制必须实现某个接口 | T HasId | 确保有id字段 |
| 限制必须为特定类型的子类型 | T | | 联合类型约束 |
1.4 完整代码示例
{
name: ;
}
greet<T >(obj: T): {
Hello, ${obj.name}; // ✅ T一定有name属性
}
greet({ name: 'Alice', age: 30 }); // ✅ 可以传入额外属性
// greet({ age: 20 }); // ❌ 错误:缺少name属性
1.5 多重约束(使用接口交叉)
merge<T , U >(a: T, b: U): T & U {
{ ...a, ...b };
}
二、条件类型( Types)
2.1 是什么?
条件类型根据类型关系T U ? X : Y在类型层面做分支判断,用于动态生成不同类型的定义。
2.2 基本语法
type <T> = T ? true : false;
type A = <'hello'>; // true
type B = <>; // false
2.3 常见使用场景
| 场景 | 示例 | 说明 |
|——|——|——|
| 过滤联合类型中的某些成员 | type <T> = T ? T : never | 保留类型 |
| 提取函数返回值类型 | type <T> = T (...args: any[]) => infer R ? R : never | 配合infer |
| 类型映射中的条件分支 | type <T> = T null | ? T : T | null | 条件添加null |
| 递归类型处理 | type <T> = { [P in keyof T]: <T[P]> } | 结合条件判断 |
2.4 配合infer提取类型
type <T> = T (infer U)[] ? U : never;
type Item1 = <[]>; //
type Item2 = <>; // never(因为不是数组)
2.5 分布式条件类型
当T是联合类型时,条件类型会自动分发到每个成员:
type <T> = T any ? T[] : never;
type = < | >; // [] | []
// 等价于 ( any ? [] : never) | ( any ? [] : never)
注意:要关闭分发行为,可用元组包裹T:
type <T> = [T] [any] ? T[] : never;
type = < | >; // ( | )[]
三、对比速查表
| 维度 | 泛型约束 | 条件类型 |
|---|---|---|
| 使用位置 | 泛型声明处(<T ...>) |
类型别名、接口、工具类型内部 |
| 关键字 | (限制) |
? :(判断) |
| 作用阶段 | 类型参数传入时检查是否满足 | 类型计算时选择结果分支 |
| 能否改变输出类型 | 不能,只做限制 | 能,动态生成新类型 |
| 典型错误 | 试图约束字面量联合类型 | 忘记分布式条件类型的行为 |
| 常见伙伴 | keyof、接口 |
infer、never、递归 |
四、高频疑难解答
Q1:为什么我不能这样写约束?
// ❌ 错误:不能约束字面量值
test<T 'a' | 'b'>(val: T) {}
// 这样写虽然语法正确,但只能传入'a'或'b',失去了泛型的灵活性
正确做法:如果需要限制值范围,使用联合类型本身,而非泛型约束。
Q2:条件类型中的never有什么用?
never在条件类型中常用来过滤类型:
type <T> = T null | ? never : T;
type = < | null | >; //
Q3:泛型约束和条件类型可以一起用吗?
可以,且非常常见:
pick<T , K keyof T>(obj: T, keys: K[]): Pick<T, K> {
// 约束T为对象,K为T的键,然后通过条件类型(Pick内置)计算结果类型
const = {} as Pick<T, K>;
keys.(key => [key] = obj[key]);
;
}
Q4:如何判断当前应该用约束还是条件类型?
约束:你想“限制能传什么类型”,让函数内部能安全使用某些属性。
条件类型:你想“根据输入类型返回不同的输出类型”,比如实现类型版的if-else。
五、实战练习
请尝试推断以下代码的输出类型:
type <T> = T ? never : T;
type = <'a' | 123 | true>; // ?
type <T> = T <infer V> ? V : T;
type = <<>>; // ?
type <T > = T;
type = <42>; // ?
参考答案:
= 123 | true(过滤掉)
= (提取内部的类型)
= 42(字面量类型)
六、官方标准参考
:条件类型( Types)
发行说明:infer关键字(2.8版本)
以上语法与行为与 5.x版本完全一致。实际开发中请确保.json中模式开启,以获得最准确的类型推断。

