函数与泛型
函数类型签名、泛型约束、条件类型与推断。
前置知识
- keyof、typeof 与索引访问类型:建议先完成前一篇的学习
学习目标
- 掌握「1. 函数重载 (Function Overloading)」的核心机制、典型用法与常见陷阱
- 掌握「2. 泛型 (Generics)」的核心机制、典型用法与常见陷阱
- 掌握「3. 泛型约束 (Generic Constraints)」的核心机制、典型用法与常见陷阱
- 掌握「4. 泛型类 (Generic Classes)」的核心机制、典型用法与常见陷阱
- 掌握「5. 泛型方法」的核心机制、典型用法与常见陷阱
1. 函数重载 (Function Overloading)
函数重载允许为同一个函数提供多个类型定义,根据传入的参数类型和数量来选择合适的类型定义。
1.1 基本函数重载
// 函数重载声明
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: number, b: string): string;
function add(a: string, b: number): string;
// 函数实现
function add(a: any, b: any): any {
return a + b;
}
// 使用示例
const sum1 = add(1, 2); // 类型为 number,值为 3
const sum2 = add('Hello, ', 'World'); // 类型为 string,值为 "Hello, World"
const sum3 = add(1, ' apples'); // 类型为 string,值为 "1 apples"
const sum4 = add('You have ', 5); // 类型为 string,值为 "You have 5"
讲解:
- 函数重载先写多个“签名声明”(只有类型、没有函数体),再写一个通用实现。
- 调用
add(1, 2)时编译器根据参数类型选择第一个签名(number 版),add("a", "b")选择第二个。 - 重载让同一个函数名对不同参数类型给出精确的返回类型。
1.2 函数重载与可选参数
// 函数重载声明
function greet(name: string): string;
function greet(name: string, age: number): string;
function greet(name: string, age?: number): string;
// 函数实现
function greet(name: string, age?: number): string {
if (age !== undefined) {
return `Hello, ${name}! You are ${age} years old.`;
}
return `Hello, ${name}!`;
}
// 使用示例
const greeting1 = greet('Alice'); // 类型为 string,值为 "Hello, Alice!"
const greeting2 = greet('Bob', 25); // 类型为 string,值为 "Hello, Bob! You are 25 years old."
讲解:
- 这是“参数个数不同”的重载:一个参数或两个参数两种签名。
- 调用
greet("Tom")返回 string,greet("Tom", 30)也返回 string。 - 重载的意义:把可选参数的不同语义用类型写清楚,调用方获得准确提示。
1.3 函数重载与联合类型
// 函数重载声明
function process(value: string): string;
function process(value: number): number;
function process(value: boolean): boolean;
// 函数实现
function process(value: string | number | boolean): string | number | boolean {
if (typeof value === 'string') {
return value.toUpperCase();
} else if (typeof value === 'number') {
return value * 2;
} else {
return !value;
}
}
// 使用示例
const result1 = process('hello'); // 类型为 string,值为 "HELLO"
const result2 = process(5); // 类型为 number,值为 10
const result3 = process(true); // 类型为 boolean,值为 false
讲解:
- 这是“参数类型不同、返回类型也不同”的重载:string 进 string 出,number 进 number 出。
- 如果不用重载而写
(value: string | number): string | number,返回类型会丢失精确性。 - 重载是“输入输出联动”的类型表达方式。
1.4 函数重载的最佳实践
- 明确类型签名: 为不同的参数组合提供清晰的类型签名。
- 实现类型兼容: 实现函数的参数类型和返回类型必须与所有重载签名兼容。
- 从具体到一般: 重载签名应该从最具体的到最一般的顺序排列。
- 避免过度使用: 只在确实需要不同类型处理逻辑时使用函数重载。
2. 泛型 (Generics)
泛型是 TypeScript 中一种强大的类型系统特性,允许我们编写可以处理多种类型的代码,而不是仅限于单一类型。
2.1 基本泛型函数
// 基本泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 使用示例
const stringOutput = identity<string>('myString'); // 类型为 string
const numberOutput = identity<number>(42); // 类型为 number
const booleanOutput = identity<boolean>(true); // 类型为 boolean
// 类型推断
const inferredString = identity('Hello'); // 类型自动推断为 string
const inferredNumber = identity(123); // 类型自动推断为 number
讲解:
function identity<T>(arg: T): T中<T>是类型占位符:调用时 T 被具体类型替换。- 返回类型
T与参数类型相同,保证“传什么类型就返回什么类型”。 - 泛型函数适合“输入输出类型一致”的工具函数,如包装、缓存、克隆。
2.2 多个泛型参数
// 多个泛型参数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second];
}
// 使用示例
const stringNumberPair = pair('hello', 42); // 类型为 [string, number]
const booleanArrayPair = pair(true, [1, 2, 3]); // 类型为 [boolean, number[]]
const objectFunctionPair = pair({ name: 'Alice' }, () => console.log('Hello')); // 类型为 [{ name: string }, () => void]
讲解:
<T, U>声明两个类型参数,分别对应两个入参的类型。- 返回
[T, U]是元组:第一个元素类型为 T,第二个为 U。 - 多泛型让函数能同时保持多个值的类型信息。
2.3 泛型接口
// 泛型接口
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
;
}
// 实现泛型接口
class NumberContainer implements Container<number> {
value: number;
constructor(value: number) {
this.value = value;
}
getValue(): number {
return this.value;
}
setValue(value: number): void {
this.value = value;
}
;
}
class StringContainer implements Container<string> {
value: string;
constructor(value: string) {
this.value = value;
}
getValue(): string {
return this.value;
}
setValue(value: string): void {
this.value = value;
}
;
}
// 使用示例
const numberContainer = new NumberContainer(42);
console.log(numberContainer.getValue()); // 42
numberContainer.setValue(100);
console.log(numberContainer.getValue()); // 100
const stringContainer = new StringContainer('Hello');
console.log(stringContainer.getValue()); // Hello
stringContainer.setValue('World');
console.log(stringContainer.getValue()); // World
讲解:
interface Container<T>是泛型接口:value: T的类型由使用方指定。- 使用时写
Container<string>,接口里的 T 就被替换成 string。 - 泛型接口是“容器类”结构的标准写法(盒子、列表、响应体)。
2.4 泛型类型别名
// 泛型类型别名
type Pair<T, U> = [T, U];
type Callback<T> = (value: T) => void;
type Transform<T, U> = (value: T) => U;
// 使用示例
const stringNumberPair: Pair<string, number> = ['age', 30];
const numberCallback: Callback<number> = (value) => console.log(`Value: ${value}`);
const stringToNumber: Transform<string, number> = (value) => parseInt(value);
numberCallback(42); // 输出: Value: 42
console.log(stringToNumber('123')); // 输出: 123
讲解:
type Pair<T, U> = [T, U]给元组类型起名并参数化。type Callback<T> = (value: T) => void参数化回调签名。- 类型别名与接口都能用泛型,区别仍是扩展方式与合并能力。
3. 泛型约束 (Generic Constraints)
泛型约束允许我们限制泛型类型参数的范围,确保它们具有某些特定的属性或方法。
flowchart LR
A["T 任意类型"] --> B{"T extends Lengthwise?"}
B -->|"有 length 属性"| C["可安全调用 arg.length"]
B -->|"没有 length"| D["编译报错:不满足约束"]
结构解析: 约束(extends)是泛型的”准入条件”:只有满足约束的类型能替换 T,换来的是函数体内可以安全使用约束提供的能力(如 .length)。
3.1 基本泛型约束
// 定义约束接口
interface Lengthwise {
length: number;
;
}
// 使用约束
function logLength<T extends Lengthwise>(arg: T): T {
console.log(`Length: ${arg.length}`);
return arg;
;
}
// 使用示例
logLength('Hello'); // 输出: Length: 5
logLength([1, 2, 3]); // 输出: Length: 3
logLength({ length: 10, value: 'test' }); // 输出: Length: 10
// 错误示例:数字没有 length 属性
// logLength(42); // 编译错误
讲解:
interface Lengthwise { length: number }定义“有 length 属性”的约束。<T extends Lengthwise>表示 T 必须满足该约束,函数内才能安全访问arg.length。- 没有约束时,编译器不允许对任意 T 调用 length——泛型不是 any。
3.2 多个泛型约束
// 定义多个约束接口
interface Lengthwise {
length: number;
}
interface HasName {
name: string;
}
// 多个约束
function processItem<T extends Lengthwise & HasName>(item: T): T {
console.log(`Name: ${item.name}, Length: ${item.length}`);
return item;
}
// 使用示例
const item = {
name: 'Test',
length: 5,
value: 42,
};
processItem(item); // 输出: Name: Test, Length: 5
讲解:
- 多个约束接口用交叉类型组合:
T extends Lengthwise & Printable。 - 这样 T 既要有 length,也要能 toString。
- 约束越多,函数体内能用的能力越多,但能传入的类型越少——需要权衡。
3.3 泛型约束与 keyof
// 使用 keyof 约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// 使用示例
const person = {
name: 'Alice',
age: 30,
email: 'alice@example.com',
};
const name = getProperty(person, 'name'); // 类型为 string
const age = getProperty(person, 'age'); // 类型为 number
const email = getProperty(person, 'email'); // 类型为 string
// 错误示例:不存在的属性
// const invalid = getProperty(person, "invalid"); // 编译错误
讲解:
K extends keyof T表示 K 必须是 T 的键之一。obj[key]的返回类型是T[K]:按传入的键精确返回对应值的类型。- 这是最经典的安全取值函数:拼错键名在编译期就报错。
3.4 泛型约束与默认值
// 带默认值的泛型约束
function createArray<T extends number | string = string>(length: number, defaultValue: T): T[] {
return Array(length).fill(defaultValue);
}
// 使用示例
const numberArray = createArray(5, 0); // 类型为 number[]
const stringArray = createArray(3, 'hello'); // 类型为 string[]
const defaultArray = createArray(2, 'test'); // 类型为 string[](使用默认类型)
讲解:
<T extends number | string = string>:约束 T 只能是 number 或 string,默认取 string。- 调用时不写类型参数,T 就按默认值 string 处理。
- 默认值让泛型“开箱即用”,需要特殊类型时再显式指定。
4. 泛型类 (Generic Classes)
泛型类允许我们创建可以处理不同类型数据的类。
4.1 基本泛型类
// 基本泛型类
class Box<T> {
private data: T;
constructor(data: T) {
this.data = data;
}
getData(): T {
return this.data;
}
setData(data: T): void {
this.data = data;
}
}
// 使用示例
const numberBox = new Box<number>(42);
console.log(numberBox.getData()); // 42
numberBox.setData(100);
console.log(numberBox.getData()); // 100
const stringBox = new Box<string>('Hello');
console.log(stringBox.getData()); // Hello
stringBox.setData('World');
console.log(stringBox.getData()); // World
讲解:
class Box<T>是泛型类:private data: T的类型由实例化时决定。new Box<number>()创建存数字的盒子,new Box<string>()存字符串。- 泛型类的字段、方法、构造参数都可以使用 T。
4.2 泛型类与约束
// 带约束的泛型类
interface Printable {
toString(): string;
}
class Printer<T extends Printable> {
print(item: T): void {
console.log(item.toString());
}
}
// 使用示例
const numberPrinter = new Printer<number>();
numberPrinter.print(42); // 输出: 42
const stringPrinter = new Printer<string>();
stringPrinter.print("Hello"); // 输出: Hello
const objPrinter = new Printer<{ name: string; toString(): string }>();
objPrinter.print({
name: "Test",
toString() { return `Object: ${this.name}`; }
}
讲解:
class Printer<T extends Printable>约束 T 必须可 toString。- 类内部因此可以安全调用
this.data.toString()。 - 约束同时作用于类的所有方法。
4.3 泛型类与静态成员
// 泛型类与静态成员
class GenericClass<T> {
private value: T;
// 静态成员不能使用泛型类型参数
static staticValue: number = 42;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
// 静态方法可以使用自己的泛型参数
static create<U>(value: U): GenericClass<U> {
return new GenericClass<U>(value);
}
}
// 使用示例
const instance = new GenericClass<string>('Hello');
console.log(instance.getValue()); // Hello
console.log(GenericClass.staticValue); // 42
const createdInstance = GenericClass.create(123);
console.log(createdInstance.getValue()); // 123
讲解:
- 静态成员(
static)不能使用类的类型参数 T,因为静态成员不属于任何实例。 - 示例中静态字段用具体类型
string[]规避了该限制。 - 记住这条规则:T 只存在于实例层面。
4.4 泛型类的继承
// 泛型类的继承
class BaseRepository<T> {
protected items: T[] = [];
add(item: T): void {
this.items.push(item);
}
getById(id: number): T | undefined {
return this.items[id];
}
}
// 继承泛型类
class User {
id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
class UserRepository extends BaseRepository<User> {
findByName(name: string): User | undefined {
return this.items.find((user) => user.name === name);
}
}
// 使用示例
const userRepo = new UserRepository();
userRepo.add(new User(1, 'Alice'));
userRepo.add(new User(2, 'Bob'));
console.log(userRepo.getById(0)?.name); // Alice
console.log(userRepo.findByName('Bob')?.id); // 2
讲解:
class BaseRepository<T>是泛型基类:protected items: T[]存放任意类型的列表。- 子类继承时指定具体类型(如
class UserRepo extends BaseRepository<User>),T 被固定。 - 泛型基类是仓储模式、DAO 模式的基础。
5. 泛型方法
泛型方法是在类或接口中定义的带有泛型参数的方法。
5.1 类中的泛型方法
// 类中的泛型方法
class Utils {
// 泛型方法
static map<T, U>(array: T[], transform: (item: T) => U): U[] {
return array.map(transform);
}
// 泛型方法与约束
static filter<T extends { active: boolean }>(array: T[]): T[] {
return array.filter(item => item.active);
}
}
// 使用示例
const numbers = [1, 2, 3, 4, 5];
const squared = Utils.map(numbers, n => n * n); // 类型为 number[]
console.log(squared); // [1, 4, 9, 16, 25]
const users = [
{ id: 1, name: "Alice", active: },
{ id: 2, name: "Bob", active: false },
{ id: 3, name: "Charlie", active: }
]
const activeUsers = Utils.filter(users); // 类型为 { id: number; name: string; active: boolean }[]
console.log(activeUsers); // [{ id: 1, name: "Alice", active: }, { id: 3, name: "Charlie", active: }]
讲解:
- 方法级别的泛型写在方法名后:
method<T>(...),与类的泛型相互独立。 - 每次调用方法时 T 都可以不同,灵活性更高。
- 类泛型 + 方法泛型可以共存,命名不冲突即可。
5.2 接口中的泛型方法
// 接口中的泛型方法
interface Collection {
// 泛型方法
<T>(items: T[]): T[];
// 带约束的泛型方法
<T extends { id: number }>(items: T[]): T[];
}
// 实现接口
const MyCollection: Collection = function<T>(items: T[]): T[] {
return items;
}
// 使用示例
const strings = MyCollection<string>(["a", "b", "c"]); // 类型为 string[]
const numbers = MyCollection<number>([1, 2, 3]); // 类型为 number[]
const users = MyCollection([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]
讲解:
- 接口里也能声明泛型方法:
add<T>(item: T): void。 - 实现该接口的类需要保持同样的泛型签名。
- 适合“集合类”接口:操作的元素类型由调用方决定。
6. 泛型工具类型 (Utility Types)
TypeScript 提供了一系列内置的泛型工具类型,用于常见的类型转换场景。
6.1 常用泛型工具类型
| 工具类型 | 描述 | 示例 |
| :------------------- | :------------------------------------- | :---------------------------------------------------------------------------------- | -------------------------------- | -------------------- | ---------- | ---- |
| Partial<T> | 将 T 中所有属性变为可选 | Partial<{ a: number; b: string }> → { a?: number; b?: string } |
| Readonly<T> | 将 T 中所有属性变为只读 | Readonly<{ a: number; b: string }> → { readonly a: number; readonly b: string } |
| Record<K, T> | 构建键为 K 类型,值为 T 类型的对象类型 | Record<string, number> → { [key: string]: number } |
| Pick<T, K> | 从 T 中选取指定的属性 K | Pick<{ a: number; b: string; c: boolean }, "a" | "b">→{ a: number; b: string } |
| Omit<T, K> | 从 T 中排除指定的属性 K | Omit<{ a: number; b: string; c: boolean }, "c"> → { a: number; b: string } |
| Exclude<T, U> | 从 T 中排除可以赋值给 U 的类型 | Exclude<"a" | "b" | "c", "a">→"b" | "c" |
| Extract<T, U> | 从 T 中提取可以赋值给 U 的类型 | Extract<"a" | "b" | "c", "a" | "b">→"a" | "b" |
| NonNullable<T> | 从 T 中排除 null 和 undefined | NonNullable<string | null | undefined>→string |
| Parameters<T> | 提取函数 T 的参数类型为元组 | Parameters<(a: number, b: string) => void> → [number, string] |
| ReturnType<T> | 提取函数 T 的返回类型 | ReturnType<() => string> → string |
6.2 泛型工具类型的使用示例
// 定义基础类型
interface User {
id: number;
name: string;
email: string;
age: number;
active: boolean;
}
// Partial<T>
type PartialUser = Partial<User>;
const partialUser: PartialUser = { id: 1, name: "Alice" };
// Readonly<T>
type ReadonlyUser = Readonly<User>;
const readonlyUser: ReadonlyUser = {
id: 1,
name: "Alice",
email: "alice@example.com",
age: 30,
active:
}
// readonlyUser.name = "Bob"; // 编译错误
// Record<K, T>
type UserRoleMap = Record<string, "admin" | "user" | "guest">;
const roleMap: UserRoleMap = {
"alice": "admin",
"bob": "user",
"charlie": "guest"
}
// Pick<T, K>
type UserEssential = Pick<User, "id" | "name" | "email">;
const essentialUser: UserEssential = {
id: 1,
name: "Alice",
email: "alice@example.com"
}
// Omit<T, K>
type UserWithoutAge = Omit<User, "age">;
const userWithoutAge: UserWithoutAge = {
id: 1,
name: "Alice",
email: "alice@example.com",
active:
}
// Exclude<T, U>
type Status = "active" | "inactive" | "pending" | "deleted";
type ActiveStatus = Exclude<Status, "deleted">; // "active" | "inactive" | "pending"
// Extract<T, U>
type NumericStatus = Extract<Status | number | boolean, number>; // number
// NonNullable<T>
type OptionalString = string | null | undefined;
type RequiredString = NonNullable<OptionalString>; // string
// Parameters<T>
type FuncParams = Parameters<(a: number, b: string) => boolean>; // [number, string]
// ReturnType<T>
type FuncReturn = ReturnType<() => { id: number; name: string }>; // { id: number; name: string }
讲解:
- 这是综合示例的数据基础:
User接口包含 id 与姓名。 - 后续的泛型工具类型都围绕它演示。
- 先有稳定数据形状,工具类型才有意义。
6.3 组合使用泛型工具类型
// 组合使用泛型工具类型
interface Product {
id: number;
name: string;
price: number;
description: string;
category: string;
stock: number;
active: boolean;
}
// 创建产品的更新类型
type ProductUpdate = Partial<Pick<Product, "name" | "price" | "description" | "stock" | "active">>;
// 使用示例
const update: ProductUpdate = {
price: 99.99,
stock: 100
}
// 创建产品的响应类型
type ProductResponse = Readonly<Omit<Product, "stock">>;
// 使用示例
const response: ProductResponse = {
id: 1,
name: "Laptop",
price: 999.99,
description: "A powerful laptop",
category: "Electronics",
active:
}
讲解:
- 示例组合多个工具类型构造出新类型(如对 Product 做 Partial/Pick)。
- 工具类型是“类型函数”,可以像函数一样嵌套组合。
- 组合时要保持语义清晰,避免一长串嵌套降低可读性。
7. 泛型的高级应用
7.1 递归泛型
// 递归泛型
interface TreeNode<T> {
value: T;
children: TreeNode<T>[];
}
// 使用示例
const tree: TreeNode<number> = {
value: 1,
children: [
{
value: 2,
children: [
{ value: 4, children: [] },
{ value: 5, children: [] },
],
},
{
value: 3,
children: [{ value: 6, children: [] }],
},
],
};
// 递归函数处理树
function traverse<T>(node: TreeNode<T>, callback: (value: T) => void): void {
callback(node.value);
node.children.forEach((child) => traverse(child, callback));
}
traverse(tree, (value) => console.log(value)); // 输出: 1, 2, 4, 5, 3, 6
讲解:
interface TreeNode<T> { children: TreeNode<T>[] }是递归类型:子节点还是同一结构。- 递归类型是树、链表等自相似结构的标准表达。
- TypeScript 支持类型递归,但要注意层级过深时的性能。
7.2 条件类型与泛型
// 条件类型与泛型
type IsArray<T> = T extends Array<any> ? : false;
type ArrayElementType<T> = T extends Array<infer U> ? U : T;
// 使用示例
type A = IsArray<string[]>; //
type B = IsArray<number>; // false
type C = ArrayElementType<string[]>; // string
type D = ArrayElementType<number>; // number
// 复杂条件类型
type DeepArrayElementType<T> = T extends Array<infer U>
? DeepArrayElementType<U>
: T;
// 使用示例
type E = DeepArrayElementType<string[][]>; // string
type F = DeepArrayElementType<number[]>; // number
type G = DeepArrayElementType<number>; // number
讲解:
T extends Array<any> ? true : false在类型层面判断 T 是否为数组。infer U从数组类型中“提取”元素类型:Array<infer U>匹配时 U 即元素类型。- 条件类型 + infer 是类型体操的核心工具。
7.3 泛型与映射类型
// 映射类型
interface Person {
name: string;
age: number;
email: string;
}
// 映射类型:将所有属性变为可选
type Optional<T> = {
[K in keyof T]?: T[K];
};
// 映射类型:将所有属性变为只读
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// 映射类型:将所有属性类型变为 string
type Stringify<T> = {
[K in keyof T]: string;
};
// 使用示例
type OptionalPerson = Optional<Person>;
type ReadonlyPerson = Readonly<Person>;
type StringifiedPerson = Stringify<Person>;
const optionalPerson: OptionalPerson = { name: 'Alice' };
const readonlyPerson: ReadonlyPerson = {
name: 'Alice',
age: 30,
email: 'alice@example.com',
};
// readonlyPerson.age = 31; // 编译错误
const stringifiedPerson: StringifiedPerson = {
name: 'Alice',
age: '30', // 类型为 string
email: 'alice@example.com',
};
讲解:
[P in keyof Person]遍历 Person 的每个键,生成新的属性集合。- 映射类型是“批量改造属性”的机制,内置 Partial/Readonly 都由此实现。
- 配合
as可以重命名或过滤键。
8. 最佳实践
8.1 泛型使用原则
- 明确类型参数名称: 使用有意义的类型参数名称,如
T表示类型,K表示键,V表示值。 - 合理使用约束: 只在需要时使用泛型约束,避免过度约束。
- 类型推断: 尽可能利用 TypeScript 的类型推断能力,减少显式类型参数的使用。
- 代码可读性: 保持泛型代码的可读性,避免过于复杂的泛型结构。
- 性能考虑: 注意泛型可能带来的编译时间增加,但通常运行时性能不受影响。
8.2 函数重载最佳实践
- 从具体到一般: 重载签名应该从最具体的到最一般的顺序排列。
- 实现兼容性: 实现函数的参数类型和返回类型必须与所有重载签名兼容。
- 避免过度使用: 只在确实需要不同类型处理逻辑时使用函数重载。
- 文档化: 为重载函数添加注释,说明不同重载的用途。
8.3 泛型工具类型使用建议
- 熟悉内置工具类型: 充分利用 TypeScript 提供的内置泛型工具类型。
- 创建自定义工具类型: 根据项目需求创建自定义的泛型工具类型。
- 组合使用: 灵活组合多个泛型工具类型以满足复杂的类型转换需求。
- 类型安全: 使用泛型工具类型确保类型安全,减少运行时错误。
9. 代码示例
9.1 泛型函数的综合使用
// 泛型函数:安全地获取对象属性
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// 泛型函数:深度克隆对象
function deepClone<T>(obj: T): T {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (obj instanceof Array) {
return obj.map((item) => deepClone(item)) as unknown as T;
}
const clonedObj = {} as T;
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
clonedObj[key] = deepClone(obj[key]);
}
}
return clonedObj;
}
// 泛型函数:创建带有默认值的数组
function createArray<T>(length: number, defaultValue: T): T[] {
return Array(length).fill(defaultValue);
}
// 使用示例
const person = {
name: 'Alice',
age: 30,
address: {
street: '123 Main St',
city: 'New York',
},
};
// 安全获取属性
const name = getProperty(person, 'name'); // 类型为 string
const age = getProperty(person, 'age'); // 类型为 number
// 深度克隆
const clonedPerson = deepClone(person);
console.log(clonedPerson.address.city); // New York
// 创建数组
const numbers = createArray(5, 0); // 类型为 number[]
const strings = createArray(3, 'hello'); // 类型为 string[]
讲解:
- 这是综合示例:
K extends keyof T+T[K]实现类型安全的属性读取。 - 调用
getProperty(user, "name")返回 string,传不存在的键直接编译报错。 - 它是前端表单取值、配置读取等场景的标准模式。
9.2 泛型类的综合使用
// 泛型队列类
class Queue<T> {
private items: T[] = [];
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
return this.items.shift();
}
peek(): T | undefined {
return this.items[0];
}
size(): number {
return this.items.length;
}
isEmpty(): boolean {
return this.items.length === 0;
}
;
}
// 泛型栈类
class Stack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T | undefined {
return this.items.pop();
}
peek(): T | undefined {
return this.items[this.items.length - 1];
}
size(): number {
return this.items.length;
}
isEmpty(): boolean {
return this.items.length === 0;
}
;
}
// 使用示例
// 数字队列
const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
numberQueue.enqueue(3);
console.log(numberQueue.dequeue()); // 1
console.log(numberQueue.peek()); // 2
// 字符串栈
const stringStack = new Stack<string>();
stringStack.push('a');
stringStack.push('b');
stringStack.push('c');
console.log(stringStack.pop()); // c
console.log(stringStack.peek()); // b
// 对象队列
interface User {
id: number;
name: string;
;
}
const userQueue = new Queue<User>();
userQueue.enqueue({ id: 1, name: 'Alice' });
userQueue.enqueue({ id: 2, name: 'Bob' });
console.log(userQueue.dequeue()?.name); // Alice
讲解:
class Queue<T>用数组实现先进先出队列:enqueue/dequeue。dequeue返回T | undefined:空队列时取不到值,调用方必须处理。- 泛型让队列可存任意类型而不损失类型信息。
9.3 泛型工具类型的综合使用
// 定义基础类型
interface APIResponse<T> {
success: boolean;
data: T;
error?: string;
}
interface User {
id: number;
name: string;
email: string;
age: number;
password: string;
}
// 创建响应类型
type UserResponse = APIResponse<Omit<User, 'password'>>;
type UserListResponse = APIResponse<Array<Omit<User, 'password'>>>;
// 创建请求类型
type CreateUserRequest = Omit<User, 'id'>;
type UpdateUserRequest = Partial<Omit<User, 'id' | 'password'>>;
// 使用示例
// 模拟 API 响应
const userResponse: UserResponse = {
success: true,
data: {
id: 1,
name: 'Alice',
email: 'alice@example.com',
age: 30,
},
};
const userListResponse: UserListResponse = {
success: true,
data: [
{
id: 1,
name: 'Alice',
email: 'alice@example.com',
age: 30,
},
{
id: 2,
name: 'Bob',
email: 'bob@example.com',
age: 25,
},
],
};
// 模拟请求数据
const createUserRequest: CreateUserRequest = {
name: 'Charlie',
email: 'charlie@example.com',
age: 35,
password: 'password123',
};
const updateUserRequest: UpdateUserRequest = {
name: 'Alice Smith',
age: 31,
};
console.log(userResponse.data);
console.log(userListResponse.data);
console.log(createUserRequest);
console.log(updateUserRequest);
讲解:
interface APIResponse<T>是响应包裹类型:success 固定,data 由 T 决定。APIResponse<User>让接口响应的数据类型一目了然。- 这是前后端接口类型约定的最佳实践。
10. 常见错误与修正(错-对对比)
10.1 泛型当 any 用
// 错误:T 没有任何约束时,函数体不能访问 .length
// function bad<T>(x: T): number { return x.length } // 报错
// 正确:用约束声明能力
function ok<T extends { length: number }>(x: T): number {
return x.length
}
讲解: 泛型不是 any:T 的能力由约束决定;需要在 T 上调用什么,就先约束什么。
10.2 重载实现签名不兼容
// 错误:实现签名必须覆盖所有重载
// function f(x: string): string
// function f(x: number): number
// function f(x: string): string { return x } // 报错:number 重载未覆盖
// 正确:实现签名用联合类型覆盖全部重载
function f(x: string): string
function f(x: number): number
function f(x: string | number): string | number {
return typeof x === "string" ? x.toUpperCase() : x.toFixed(2)
}
讲解: 重载分两层:对外是多个精确签名,对内是一个兼容所有签名的实现;实现签名必须能接收所有重载的参数。
10.3 keyof 约束写错键名
interface User3 { name: string; age: number }
// 错误:键名拼错,编译期报错
// getProperty(user3, "naem")
// 正确:K extends keyof T 保证键名合法
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const user3: User3 = { name: "A", age: 20 }
const name3: string = getProperty(user3, "name") // 类型精确为 string
讲解: keyof T 把键集合变成类型,K extends keyof T 让”拼错键名”变成编译错误,而不是运行期 undefined。
10.4 泛型类的静态成员误用 T
// 错误:静态成员不能使用类的类型参数 T
// class Bad<T> { static data: T } // 报错
// 正确:静态成员使用具体类型,或把 T 用在实例成员上
class Good<T> {
static data: string = ""
value: T
constructor(value: T) { this.value = value }
}
讲解: 静态成员属于类本身、不属于某个实例,因此不能用”实例化时才确定”的 T。
函数声明
速查索引:从这里到文件末尾与正文内容重复,按需查阅,第一遍阅读可以跳过。
基本写法:函数声明
function <函数名>(<参数>: <类型>): <返回类型> { <语句> }
// 函数声明
function add(a: number, b: number): number {
return a + b
}
讲解:
function add(a: number, b: number): number是函数声明:function 关键字开头。- 函数声明有提升(hoisting),可以在声明之前调用。
- 参数与返回值都要标注类型,这是 TS 与 JS 的基本差异。
基本写法:函数表达式
const <函数名> = function(<参数>: <类型>): <返回类型> { <语句> }
// 函数表达式
const add = function(a: number, b: number): number {
return a + b
}
讲解:
const add = function(...)把匿名函数赋给变量,是函数表达式。- 与函数声明的区别:表达式按赋值顺序执行,没有提升。
- 两者的类型标注方式相同。
基本写法:箭头函数
const <函数名> = (<参数>: <类型>): <返回类型> => <表达式>
// 箭头函数
const add = (a: number, b: number): number => a + b
讲解:
const add = (a: number, b: number): number => a + b是箭头函数。- 箭头函数不绑定自己的 this,回调场景更安全。
- 现代代码风格优先使用箭头函数。
函数类型
基本写法:使用 type 定义函数类型
type <函数类型> = (<参数>: <类型>) => <返回类型>
// 使用 type 定义函数类型
type MathFunc = (a: number, b: number) => number
讲解:
type MathFunc = (a: number, b: number) => number定义可复用的函数类型。- 之后多个变量、参数可以复用 MathFunc,签名只需写一次。
- 这是函数类型最简洁的定义方式。
换行写法:使用 interface 定义函数类型
interface <函数类型> {
(<参数>: <类型>): <返回类型>
}
// 使用 interface 定义函数类型
interface MathFunc {
(a: number, b: number): number
}
讲解:
- 同一函数类型也可以用 interface 定义,效果等价。
- 选择建议:追求简洁用 type,需要声明合并才用 interface。
- 团队内保持一致即可。
基本写法:使用函数类型
let <变量>: <函数类型> = (<参数>) => <表达式>
// 使用函数类型注解
let add: MathFunc = (a, b) => a + b
讲解:
let add: MathFunc = (a, b) => a + b:变量标注为 MathFunc 后,箭头函数参数类型自动推断。- 实现与签名不一致时编译器报错。
- 这种“先定签名、再写实现”的方式适合接口回调。
可选参数
基本写法:可选参数
function <函数>(<参数1>: <类型>, <参数2>?: <类型>): <返回类型> { <语句> }
// 可选参数(必须放在必选参数后)
function greet(name: string, greeting?: string): string {
return `${greeting || "Hello"}, ${name}`
}
讲解:
greeting?: string是可选参数:调用时可以省略。- 可选参数必须放在必选参数之后,否则调用顺序会产生歧义。
- 函数体内用
greeting || "Hello"提供默认行为。
默认参数
基本写法:默认参数
function <函数>(<参数>: <类型> = <默认值>): <返回类型> { <语句> }
// 默认参数值
function greet(name: string = "World"): string {
return `Hello, ${name}`
}
讲解:
name: string = "World"是默认参数:不传时自动使用默认值。- 与可选参数的区别:默认参数在类型上仍然是 string(非 undefined)。
- 推荐优先用默认参数而不是手动判断。
剩余参数
基本写法:剩余参数
function <函数>(...<参数>: <类型>[]): <返回类型> { <语句> }
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0)
}
讲解:
...numbers: number[]收集所有剩余实参为数组。sum(1, 2, 3, 4)中 numbers 是[1, 2, 3, 4]。reduce累加求和,初始值 0 保证空调用返回 0。
函数重载
换行写法:函数重载签名
function <函数>(<参数>: <类型1>): <返回类型1>
function <函数>(<参数>: <类型2>): <返回类型2>
function <函数>(<参数>: <类型>): <返回类型> { <语句> }
// 函数重载
function process(data: number): string
function process(data: string): number
function process(data: number | string): string | number {
if (typeof data === "number") {
return String(data)
}
return data.length
}
讲解:
- 这里用两行重载声明 + 实现展示“类型不同返回不同”。
- 实现签名必须兼容所有重载(参数与返回取联合)。
- 重载的顺序很重要:范围窄的写前面。
泛型函数
基本写法:定义泛型函数
function <函数><<类型参数>>(<参数>: <类型参数>): <类型参数> { <语句> }
// 定义泛型函数
function identity<T>(value: T): T {
return value
}
讲解:
- 速查段复述基础泛型:
identity<T>原样返回参数。 - 与前面完整示例对比,写法完全一致。
- 复习要点:T 由调用方决定。
基本写法:使用泛型函数
<函数><<类型>>(<值>)
// 使用泛型函数(显式指定类型)
let result = identity<string>("hello")
讲解:
identity<string>("hello")显式指定 T 为 string。- 显式指定适合编译器无法推断的场景。
- 返回值类型此时是 string。
基本写法:类型推断泛型
<函数>(<值>)
// 使用泛型函数(自动推断类型)
let result = identity("hello") // T 推断为 string
讲解:
identity("hello")省略类型参数,编译器从实参推断 T = string。- 日常开发优先自动推断,代码更简洁。
- 推断失败(如空数组)时再显式指定。
多类型参数泛型
单行写法:多类型参数泛型函数
function <函数><<T>, <U>>(<参数1>: <T>, <参数2>: <U>): [<T>, <U>] { <语句> }
// 多类型参数泛型函数
function pair<T, U>(first: T, second: U): [T, U] {
return [first, second]
}
讲解:
pair<T, U>接收两个不同类型的值,返回元组。- 速查段与完整示例一致,
[T, U]保持两个类型不丢失。 - 典型用途:键值对、坐标、映射条目。
换行写法:多类型参数泛型函数
function <函数><
<T>,
<U>,
>(<参数1>: <T>, <参数2>: <U>): [<T>, <U>] { <语句> }
// 多类型参数泛型函数(换行书写)
function pair<
T,
U,
>(first: T, second: U): [T, U] {
return [first, second]
}
讲解:
- 类型参数列表过长时可以换行书写,效果相同。
<T,换行后继续写U>只是排版差异。- 团队格式化工具会自动统一风格。
泛型接口
换行写法:定义泛型接口
interface <接口名><<T>> {
<属性>: <T>
}
// 定义泛型接口
interface Container<T> {
value: T
}
讲解:
- 速查段再次展示泛型接口
Container<T>。 - 复习:接口的 T 在使用时替换。
- 与类型别名版本二选一即可。
基本写法:使用泛型接口
let <变量>: <接口名><<类型>> = { <属性>: <值> }
// 使用泛型接口
let container: Container<string> = { value: "hello" }
讲解:
let container: Container<string> = { value: "hello" }实例化泛型接口。- 此时 value 必须是 string。
- 写错类型会立刻报错。
泛型类
换行写法:定义泛型类
class <类名><<T>> {
private <属性>: <T>[]
<方法>(<参数>: <T>): void { <语句> }
}
// 定义泛型类
class Stack<T> {
private items: T[] = []
push(item: T): void {
this.items.push(item)
}
pop(): T | undefined {
return this.items.pop()
}
}
讲解:
class Stack<T>是泛型栈:push/pop 操作元素类型为 T。- 速查段与完整示例一致。
- 泛型类适合所有容器类数据结构。
基本写法:使用泛型类
let <变量> = new <类名><<类型>>()
// 使用泛型类
let stack = new Stack<number>()
stack.push(1)
讲解:
new Stack<number>()创建数字栈,stack.push(1)合法、push("x")报错。- 实例化后 T 固定为该实例的专属类型。
- 这是“一次定义、多次实例化不同类型”的核心价值。
泛型约束
换行写法:使用 extends 约束泛型
interface <接口> { <属性>: <类型> }
function <函数><<T> extends <接口>>(<参数>: <T>): <返回类型> { <语句> }
// 泛型约束(限制类型必须包含指定属性)
interface HasLength {
length: number
}
function log_length<T extends HasLength>(value: T): void {
console.log(value.length)
}
讲解:
interface HasLength { length: number }定义约束。<T extends HasLength>只接受有 length 的类型(string/数组/类数组)。- 约束让泛型“安全地使用能力”。
基本写法:使用 keyof 约束泛型
function <函数><<T>, <K> extends keyof <T>>(<参数>: <T>, <键>: <K>): <返回类型> { <语句> }
// 使用 keyof 约束泛型
function get_property<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
讲解:
- 速查段复述
get_property<T, K extends keyof T>。 - 复习:K 必须是 T 的键,返回值
T[K]精确对应。 - 拼错键名在编译期暴露。
泛型默认类型
基本写法:泛型默认类型
function <函数><<T> = <默认类型>>(<参数>: <T>): <返回类型> { <语句> }
// 泛型默认类型
function create_array<T = string>(length: number, value: T): T[] {
return Array(length).fill(value)
}
讲解:
<T = string>给类型参数默认值。- 不指定时 T 为 string,指定时用指定类型。
- 默认值让 API 对多数调用方零配置。
换行写法:多泛型默认类型
function <函数><
<T> = <默认类型1>,
<U> = <默认类型2>,
>(<参数1>: <T>, <参数2>: <U>): <返回类型> { <语句> }
// 多泛型默认类型
function create_pair<
T = string,
U = number,
>(first: T, second: U): [T, U] {
return [first, second]
}
讲解:
- 多个类型参数都可以有默认值,按需换行书写。
- 注意:有默认值的参数要放在后面,调用时才能省略。
- 与函数默认参数规则一致。
泛型工具类型
基本写法:使用 Partial 工具类型
type <别名> = Partial<<接口>>
// 使用 Partial 使所有属性可选
interface User {
name: string
age: number
}
type PartialUser = Partial<User>
讲解:
Partial<User>把 User 所有属性变可选。- 适合“部分更新”接口的入参类型。
- 速查段与前面工具类型章节呼应。
基本写法:使用 Readonly 工具类型
type <别名> = Readonly<<接口>>
// 使用 Readonly 使所有属性只读
type ReadonlyUser = Readonly<User>
讲解:
Readonly<User>把所有属性变为只读。- 适合不可变数据与配置对象。
- 编译期约束,运行时不拦截。
基本写法:使用 Pick 工具类型
type <别名> = Pick<<接口>, "<属性1>" | "<属性2>">
// 使用 Pick 选取部分属性
type UserBasic = Pick<User, "name" | "age">
讲解:
Pick<User, "name" | "age">只保留指定键。- 列表页“只显示部分字段”时常用。
- 与 Omit 是互补操作。
基本写法:使用 Record 工具类型
type <别名> = Record<<键类型>, <值类型>>
// 使用 Record 创建键值对类型
type UserMap = Record<string, User>
讲解:
Record<string, User>表达“字符串键 → User 值”的字典。- 键集合可精确限定:
Record<'a'|'b', number>。 - 比手写索引签名可读性更好。
泛型与数组
基本写法:泛型数组函数
function <函数><<T>>(<参数>: <T>[]): <T> { <语句> }
// 泛型数组函数
function first<T>(items: T[]): T {
return items[0]
}
讲解:
first<T>(items: T[]): T返回数组第一个元素,类型为 T。- 空数组时返回 undefined,实际项目应返回
T | undefined。 - 泛型让工具函数适配所有类型数组。
基本写法:泛型数组方法
function <函数><<T>>(<参数>: <T>[], <函数>: (<项>: <T>) => boolean): <T>[] { <语句> }
// 泛型数组方法
function filter<T>(items: T[], predicate: (item: T) => boolean): T[] {
return items.filter(predicate)
}
讲解:
filter<T>(items, predicate)用回调过滤数组,保持元素类型 T。predicate: (item: T) => boolean是类型化的过滤条件。- 这与 JS 内置 filter 的行为一致,只是类型更明确。
this 类型
换行写法:使用 this 类型
class <类名> {
<方法>(<参数>: <类型>): this { return this }
}
// 使用 this 类型实现链式调用
class Calculator {
private value = 0
add(n: number): this {
this.value += n
return this
}
multiply(n: number): this {
this.value *= n
return this
}
}
讲解:
- 方法返回
this实现链式调用:calc.add(1).multiply(2).value()。 - 返回类型写
this(多态 this),子类调用时自动收窄为子类类型。 - 这是构建器模式(Builder)的标准类型写法。
高阶函数
基本写法:高阶函数
function <函数>(<函数参数>: (<参数>: <类型>) => <返回类型>): <返回类型> { <语句> }
// 高阶函数(函数作为参数)
function apply(func: (x: number) => number, value: number): number {
return func(value)
}
讲解:
apply(func: (x: number) => number, value: number)把函数当参数传。- 参数 func 的类型标注让回调签名可检查。
- 高阶函数是函数式编程的基石。
基本写法:返回函数的函数
function <函数>(<参数>): (<参数>: <类型>) => <返回类型> { return <函数> }
// 返回函数的函数
function create_multiplier(factor: number): (x: number) => number {
return (x: number) => x * factor
}
讲解:
create_multiplier(factor)返回闭包函数:(x) => x * factor。- 返回类型
(x: number) => number明确描述“产出的函数长什么样”。 - 这是工厂函数模式:用参数生成定制函数。
泛型与 Promise
换行写法:泛型 Promise 函数
async function <函数><<T>>(): Promise<<T>> { return <值> }
// 泛型 Promise 函数
async function fetch_data<T>(url: string): Promise<T> {
const response = await fetch(url)
return response.json()
}
讲解:
async function fetch_data<T>(url): Promise<T>泛型化异步请求。- 调用方指定 T 后,
await的结果直接是 T 类型,无需再断言。 - 这是封装 fetch/axios 的标准模式。
基本写法:使用泛型 Promise
let <变量>: Promise<<类型>> = <函数>()
// 使用泛型 Promise
let data: Promise<User> = fetch_data<User>("/api/user")
讲解:
fetch_data<User>("/api/user")显式指定响应类型为 User。let data: Promise<User>保持异步类型链完整。- 类型安全从请求到使用贯穿全程。
条件类型与泛型
基本写法:条件类型
type <类型> = <T> extends <条件> ? <真类型> : <假类型>
// 条件类型
type IsString<T> = T extends string ? true : false
讲解:
type IsString<T> = T extends string ? true : false是类型级判断。- 传入 string 得到 true,传入其他类型得到 false。
- 速查段与完整示例一致,是条件类型入门款。
基本写法:使用条件类型
type <别名> = <类型函数><<参数类型>>
// 使用条件类型
type A = IsString<string> // true
type B = IsString<number> // false
讲解:
IsString<string>计算为 true,IsString<number>计算为 false。- 条件类型在编译期“求值”,结果可继续参与其他类型运算。
- 这是类型编程的基本单元。
泛型与类方法
换行写法:泛型类方法
class <类名><<T>> {
<方法><<U>>(<参数>: <U>): <返回类型> { <语句> }
}
// 泛型类方法
class DataProcessor<T> {
private data: T[] = []
add(item: T): void {
this.data.push(item)
}
transform<U>(fn: (item: T) => U): U[] {
return this.data.map(fn)
}
}
讲解:
class DataProcessor<T>中方法操作 T 类型的数据数组。addItem、getItem等方法复用同一个 T。- 类泛型让“处理器”类型一致。
函数类型推断
基本写法:从函数推断返回类型
type <别名> = ReturnType<typeof <函数>>
// 从函数推断返回类型
function get_user() {
return { name: "Alice", age: 30 }
}
type User = ReturnType<typeof get_user>
讲解:
get_user()没有标注返回类型,编译器从return推断为{ name: string; age: number }。- 用
ReturnType<typeof get_user>可以提取该返回类型。 - 自动推断省事,但公共 API 建议显式标注。
基本写法:从函数推断参数类型
type <别名> = Parameters<typeof <函数>>
// 从函数推断参数类型
function greet(name: string, age: number): void {}
type GreetParams = Parameters<typeof greet> // [string, number]
讲解:
- 函数参数必须显式标注类型(否则隐式 any 报错)。
Parameters<typeof greet>可以提取参数元组类型。- 内置工具类型与 typeof 配合能“反推”类型。
泛型与映射类型
换行写法:泛型映射类型
type <类型><<T>> = {
[P in keyof T]: <新类型>
}
// 泛型映射类型
type Stringify<T> = {
[P in keyof T]: string
}
讲解:
type Stringify<T> = { [P in keyof T]: string }把每个属性值变成 string。- 输入
{ name: string; age: number },输出{ name: string; age: string }。 - 映射类型是类型“批量转换”的引擎。
换行写法:使用泛型映射类型
type <别名> = <类型><<接口>>
// 使用泛型映射类型
interface User {
name: string
age: number
}
type StringUser = Stringify<User> // { name: string, age: string }
讲解:
Stringify<User>作用于具体接口,验证转换效果。- 属性名保留、值的类型被替换。
- 这是表单序列化、显示格式化场景的类型利器。
TypeScript 5.x 新特性
进阶预览(第一遍可跳过):本节是 TS 5.x 新特性的速览(标准装饰器、const 类型参数、NoInfer、erasableSyntaxOnly 等),需要项目经验才能体会其价值。零基础读完上面的速查区即可,回头再看本节。
基本写法:TypeScript 5.0 装饰器
function <decorator>(<target>, <context>) { }
// 定义符合 Stage 3 标准的装饰器函数
function log(target, context) {
console.log(`装饰: ${context.name}`)
}
讲解:
- 标准装饰器接收
target与context两个参数。 context.name是被装饰成员的名称。- 新版装饰器不需要 experimentalDecorators 选项。
基本写法:TypeScript 5.0 const 类型参数
<const T>
// 使用 const 类型参数锁定传入数组的字面量类型
function first_of<T extends readonly unknown[]>(arr: const T): T[0] {
return arr[0]
}
const v = first_of([1, 2, 3] as const) // 类型为字面量 1
讲解:
<const T extends readonly unknown[]>保留数组的字面量类型。- 返回
T[0]是第一个元素的精确类型。 - 与
as const思路一致,泛型层面锁定字面量。
基本写法:TypeScript 5.1 函数返回类型分离声明
function <函数名>(<参数>): <返回类型> { return <表达式> }
// 返回类型与函数体解耦检查,便于提前捕获类型错误
function build_user(id: number): User {
return { id, name: "Tom" }
}
讲解:
- 先写返回类型
: User,函数体返回对象必须匹配。 - 字段少、多、错都会被编译期捕获。
- “先声明后实现”让意图清晰。
基本写法:TypeScript 5.2 using 资源管理
using <resource> = <表达式>
// 使用 using 声明在作用域结束时自动释放资源
function process() {
using resource = get_resource()
// 函数结束时自动调用 resource[Symbol.dispose]()
}
讲解:
using resource = get_resource()在作用域退出时自动释放。- 释放逻辑来自对象的
[Symbol.dispose]方法。 - 避免手写 try/finally 的资源泄漏风险。
基本写法:TypeScript 5.4 NoInfer 工具类型
NoInfer<T>
// 使用 NoInfer 阻止类型参数的逆向推断
function create_pair<T>(first: T, second: NoInfer<T>): [T, T] {
return [first, second]
}
讲解:
second: NoInfer<T>声明该位置不参与 T 的推断。- T 只由 first 决定,second 仅校验一致性。
- 防止
create_pair("a", 1)把 T 推断成 string | number。
基本写法:TypeScript 5.5 推断类型谓词
(<param>) => <param> is <类型>
// 自动推断类型谓词,无需显式标注 is 类型守卫
const is_string = (x: unknown) => typeof x === "string"
// 推断为 (x: unknown) => x is string
讲解:
typeof x === "string"的布尔返回自动成为类型谓词。- 推断签名是
(x: unknown) => x is string。 - filter 后数组自动收窄为 string[]。
基本写法:TypeScript 5.6 不允许真值比较
if (<cond> === true)
// 启用 --strictBooleanExpressions 后必须显式比较布尔值
function process(value?: boolean) {
if (value === true) {
console.log("值为 true")
}
}
讲解:
- 严格模式下
if (value)对 optional boolean 不合法,必须写if (value === true)。 - 显式比较避免把 undefined 当成 false 的歧义。
- 这是 lint 级严格检查的示例。
基本写法:TypeScript 5.7 默认导入解析约束
import <名称> from "<模块>"
// 5.7 默认使用 bundler 解析模式,类型导入需显式标注 type 修饰符
import type { User } from "./types"
import { UserService } from "./service"
讲解:
import type { User }明确表示只导入类型,编译时被擦除。- 值导入(
import { UserService })保留运行时引用。 verbatimModuleSyntax下混用会报错,区分两者是必修课。
基本写法:TypeScript 5.8 —erasableSyntaxOnly 选项
// tsconfig.json 中设置 "erasableSyntaxOnly": true
// 启用后仅允许类型层面的可擦除语法,禁止 enum 和 namespace 等运行时构造
type Status = "active" | "inactive"
// 以下写法将被禁止:
// enum Status { Active, Inactive }
讲解:
erasableSyntaxOnly只允许编译后可擦除的语法。enum与namespace会生成运行时代码,被该选项禁止。- 替代方案:用
as const对象或字面量联合表达枚举。
11. 自测(小测验)
第 1 题(填空):function identity<T>(x: T): T 中,identity("hi") 的返回类型是什么?
第 2 题(单选):想让泛型函数能安全访问 arg.length,应该怎么做?
第 3 题(判断):函数重载的实现签名可以是任意签名,对吗?
点击查看答案
string(编译器从实参自动推断 T)。- 给 T 加约束:
function f<T extends { length: number }>(arg: T): number。 - 不对。实现签名必须兼容所有重载签名(参数取联合、返回取联合),否则编译报错。