extends 在 TypeScript 中有三种不同的用法:接口继承、条件类型、泛型约束。泛型约束是最常用的模式,通过 T extends SomeType 限制泛型参数的类型范围,让函数体内可以安全地访问泛型参数的属性。
// extends 的三种用法与泛型约束实战
// 用法一:接口继承(interface extends)
interface Animal { name: string; }
interface Dog extends Animal { breed: string; }
// Dog 继承 Animal 的所有属性
// 用法二:条件类型(T extends U ? X : Y)
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
// 用法三:泛型约束(T extends Constraint)
// 约束 T 必须包含特定属性
function getLength<T extends { length: number }>(arg: T): number {
return arg.length; // 安全,因为约束保证了 length 存在
}
getLength('hello'); // 5
getLength([1, 2, 3]); // 3
// getLength(123); // 错误!number 没有 length
// extends keyof:约束泛型为对象的键
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: 'Alice', email: 'alice@test.com' };
const id = getProperty(user, 'id'); // number
const name = getProperty(user, 'name'); // string
// getProperty(user, 'age'); // 错误!"age" 不是 "id" | "name" | "email"
// extends object vs extends Record<string, any>
// extends object:排除原始类型(string/number/boolean 等)
function fn1<T extends object>(arg: T) {}
fn1({}); // OK
fn1([]); // OK
// fn1('hello'); // 错误!string 不是 object
// extends Record<string, any>:只接受有字符串索引签名的对象
function fn2<T extends Record<string, any>>(arg: T) {}
fn2({ a: 1 }); // OK
// fn2([]); // 错误!数组没有 string 索引签名
// 多重约束:T 必须同时满足多个条件
interface HasId { id: number; }
interface HasName { name: string; }
function process<T extends HasId & HasName>(item: T) {
console.log(item.id, item.name); // 两个属性都安全可用
}
K extends keyof T 是最常用的泛型约束模式,确保传入的 key 是对象 T 的合法属性名,同时推导出对应的值类型。& 组合多个约束,如 T extends A & B,T 必须同时满足 A 和 B。extends 在函数签名中,条件类型中的 extends 在类型定义中,但语法相同。// 实战:封装一个通用的排序函数
interface Sortable {
id: number;
createdAt: string;
[key: string]: unknown;
}
// T 必须是 Sortable 的子类型,K 必须是 T 的键
function sortBy<T extends Sortable, K extends keyof T>(
list: T[],
key: K,
order: 'asc' | 'desc' = 'asc'
): T[] {
return [...list].sort((a, b) => {
const va = a[key];
const vb = b[key];
if (typeof va === 'string' && typeof vb === 'string') {
return order === 'asc' ? va.localeCompare(vb) : vb.localeCompare(va);
}
return order === 'asc' ? Number(va) - Number(vb) : Number(vb) - Number(va);
});
}
interface Product extends Sortable {
name: string;
price: number;
stock: number;
}
const products: Product[] = [
{ id: 1, name: 'A', price: 100, stock: 10, createdAt: '2024-01-01' },
{ id: 2, name: 'B', price: 50, stock: 20, createdAt: '2024-01-02' },
];
const sorted = sortBy(products, 'price', 'desc');
// sortBy(products, 'unknown'); // 编译错误!