====== 第十三章:类型推断 ====== ===== 本章概述 ===== 类型推断(Type Inference)是 TypeScript 的核心特性之一,它允许编译器在没有显式类型注解的情况下自动推断出变量的类型。本章将深入探讨类型推断的工作原理、上下文类型推断、类型加宽(Widening)与类型缩小(Narrowing)等重要概念,帮助你编写既简洁又类型安全的代码。 ===== 13.1 类型推断基础 ===== ==== 13.1.1 什么是类型推断 ==== 类型推断是指 TypeScript 编译器根据变量的初始化值、函数返回值、上下文信息等自动推导出类型的过程。 // 变量类型推断 let name = "Alice"; // 推断为 string let age = 25; // 推断为 number let isActive = true; // 推断为 boolean let hobbies = ["reading", "coding"]; // 推断为 string[] // 错误示例 - 类型推断后不能改变类型 name = 42; // Error: Type 'number' is not assignable to type 'string' ==== 13.1.2 基本推断规则 ==== **从初始化值推断:** // 字符串 const message = "Hello"; // string // 数字 const count = 42; // number // 布尔值 const enabled = false; // boolean // 数组 const numbers = [1, 2, 3]; // number[] const mixed = [1, "two", true]; // (string | number | boolean)[] // 对象 const person = { name: "Alice", age: 25 }; // { name: string; age: number } ===== 13.2 变量声明的类型推断 ===== ==== 13.2.1 let 与 const 的区别 ==== let 和 const 的声明会影响类型推断的结果: // let - 推断为宽泛的类型 let letString = "hello"; // string let letNumber = 42; // number letString = "world"; // OK letString = "foo"; // OK // const - 推断为具体的字面量类型 const constString = "hello"; // "hello" (字面量类型) const constNumber = 42; // 42 (字面量类型) // constString = "world"; // Error: 不能重新赋值 // 但 const 对象/数组的属性可以修改 const person = { name: "Alice", age: 25 }; // { name: string; age: number } person.name = "Bob"; // OK - 属性可修改 // person = {}; // Error - 不能重新赋值 ==== 13.2.2 解构中的类型推断 ==== // 数组解构 const [first, second] = [1, 2]; // first: number, second: number const [a, ...rest] = [1, 2, 3]; // a: number, rest: number[] // 对象解构 const person = { name: "Alice", age: 25, email: "alice@example.com" }; const { name, age } = person; // name: string, age: number const { name: userName } = person; // userName: string const { email = "default@example.com" } = person; // email: string // 嵌套解构 const user = { profile: { firstName: "Alice", lastName: "Smith" }, settings: { theme: "dark" } }; const { profile: { firstName, lastName } } = user; // firstName: string, lastName: string ===== 13.3 函数相关的类型推断 ===== ==== 13.3.1 函数返回值推断 ==== // 简单返回值推断 function add(a: number, b: number) { return a + b; // 推断返回值为 number } // 条件返回值推断 function getValue(condition: boolean) { if (condition) { return 42; // number } return "error"; // string } // 推断返回值为: number | string // 复杂返回值推断 function createUser(name: string, age: number) { return { name, // string age, // number createdAt: new Date() // Date }; // 推断为: { name: string; age: number; createdAt: Date } } ==== 13.3.2 箭头函数的类型推断 ==== // 基本箭头函数 const multiply = (a: number, b: number) => a * b; // 返回 number // 数组方法的回调推断 const numbers = [1, 2, 3, 4, 5]; const doubled = numbers.map(n => n * 2); // number[] // 自动推断 n 为 number,返回值也为 number const evens = numbers.filter(n => n % 2 === 0); // number[] // 自动推断返回 boolean const sum = numbers.reduce((acc, n) => acc + n, 0); // number // 推断 acc 和 n 为 number,返回 number // 对象数组处理 const users = [ { name: "Alice", age: 25 }, { name: "Bob", age: 30 } ]; const names = users.map(u => u.name); // string[] const adults = users.filter(u => u.age >= 18); // typeof users ===== 13.4 上下文类型推断 ===== ==== 13.4.1 什么是上下文类型 ==== 上下文类型(Contextual Typing)是指 TypeScript 根据变量被赋值的位置的上下文来推断类型的机制。 // 事件处理器 - 从上下文推断参数类型 window.onmousedown = function(event) { // TypeScript 推断 event 为 MouseEvent console.log(event.button); // OK console.log(event.target); // OK // console.log(event.foo); // Error: Property 'foo' does not exist }; // 明确的类型 vs 上下文推断 type EventHandler = (event: MouseEvent) => void; const handler: EventHandler = (e) => { // e 被推断为 MouseEvent console.log(e.clientX, e.clientY); }; ==== 13.4.2 回调函数的上下文推断 ==== interface User { name: string; age: number; } // 处理函数类型 function processUsers( users: User[], callback: (user: User, index: number) => string ): string[] { return users.map(callback); } // 使用时 - 参数类型从上下文自动推断 const results = processUsers( [{ name: "Alice", age: 25 }], (user, index) => { // user 自动推断为 User 类型 // index 自动推断为 number 类型 return `${index}: ${user.name}`; } ); ==== 13.4.3 对象字面量的上下文推断 ==== interface Config { host: string; port: number; ssl?: boolean; } function setupServer(config: Config): void { console.log(`Server: ${config.host}:${config.port}`); } // 对象字面量 - 根据 Config 接口推断 setupServer({ host: "localhost", // 必须是 string port: 3000, // 必须是 number // foo: "bar" // Error: Object literal may only specify known properties }); // 类型推断在数组中 const configs: Config[] = [ { host: "localhost", port: 3000 }, { host: "example.com", port: 443, ssl: true } // { host: 123, port: "80" } // Error: 类型不匹配 ]; ===== 13.5 类型加宽(Widening)===== ==== 13.5.1 字面量类型的加宽 ==== 类型加宽是指 TypeScript 将具体的字面量类型扩展为更一般的类型。 // const - 保持字面量类型 const constHello = "hello"; // 类型: "hello" const constNum = 42; // 类型: 42 const constBool = true; // 类型: true // let - 类型加宽 let letHello = "hello"; // 类型: string (从 "hello" 加宽) let letNum = 42; // 类型: number (从 42 加宽) let letBool = true; // 类型: boolean (从 true 加宽) letHello = "world"; // OK - 因为类型是 string letNum = 100; // OK - 因为类型是 number ==== 13.5.2 对象和数组的加宽 ==== // 对象字面量 - 属性类型会加宽 const config = { env: "development", // 类型: string (而非 "development") port: 3000 // 类型: number (而非 3000) }; // 使用 as const 阻止加宽 const strictConfig = { env: "development", // 类型: "development" port: 3000 // 类型: 3000 } as const; // 数组也会加宽 const directions = ["north", "south", "east", "west"]; // 类型: string[] (而非 ("north" | "south" | "east" | "west")[]) const strictDirections = ["north", "south", "east", "west"] as const; // 类型: readonly ["north", "south", "east", "west"] ==== 13.5.3 显式类型注解阻止加宽 ==== // 使用显式类型阻止加宽 type Environment = "development" | "staging" | "production"; const env: Environment = "development"; // 类型: Environment // env = "test"; // Error: "test" 不在联合类型中 // 函数返回值阻止加宽 function getConfig(): { mode: "dev" | "prod"; debug: boolean } { return { mode: "dev", // 不加宽为 string debug: true // 不加宽为 boolean }; } // 使用 satisfies 操作符(TypeScript 4.9+) const apiConfig = { endpoint: "/api", timeout: 5000 } satisfies { endpoint: string; timeout: number }; // 保持具体类型,同时检查是否符合接口 ===== 13.6 类型缩小(Narrowing)===== ==== 13.6.1 typeof 类型缩小 ==== function processValue(value: string | number | boolean) { // typeof 类型缩小 if (typeof value === "string") { // value 被缩小为 string 类型 console.log(value.toUpperCase()); } else if (typeof value === "number") { // value 被缩小为 number 类型 console.log(value.toFixed(2)); } else { // value 被缩小为 boolean 类型 console.log(value ? "yes" : "no"); } } ==== 13.6.2 真值缩小 ==== function printString(str: string | null | undefined) { // 真值检查缩小 if (str) { // str 被缩小为 string 类型(排除 null 和 undefined) console.log(str.toUpperCase()); } // 等价于 if (str !== null && str !== undefined) { console.log(str.toUpperCase()); } } // 数组过滤示例 function getValidStrings(items: (string | null)[]): string[] { return items.filter((item): item is string => item !== null); } ==== 13.6.3 等值缩小 ==== type Status = "loading" | "success" | "error"; function handleStatus(status: Status) { // 等值检查缩小 if (status === "loading") { console.log("Loading..."); return; } // 这里 status 被缩小为 "success" | "error" if (status === "success") { console.log("Success!"); } else { // 这里 status 被缩小为 "error" console.log("Error!"); } } ==== 13.6.4 in 操作符缩小 ==== type Cat = { meow: () => void }; type Dog = { bark: () => void }; function makeSound(animal: Cat | Dog) { if ("meow" in animal) { // animal 被缩小为 Cat 类型 animal.meow(); } else { // animal 被缩小为 Dog 类型 animal.bark(); } } ==== 13.6.5 instanceof 缩小 ==== function processData(data: Date | string | number) { if (data instanceof Date) { // data 被缩小为 Date 类型 console.log(data.toISOString()); } else if (typeof data === "string") { // data 被缩小为 string 类型 console.log(data.toUpperCase()); } else { // data 被缩小为 number 类型 console.log(data.toFixed(2)); } } // 自定义类 class ApiError extends Error { constructor(message: string, public code: number) { super(message); } } function handleError(error: Error) { if (error instanceof ApiError) { console.log(`API Error ${error.code}: ${error.message}`); } else { console.log(`Generic Error: ${error.message}`); } } ==== 13.6.6 自定义类型保护 ==== // 定义类型 interface Fish { type: "fish"; swim: () => void; } interface Bird { type: "bird"; fly: () => void; } type Animal = Fish | Bird; // 自定义类型保护函数 function isFish(animal: Animal): animal is Fish { return (animal as Fish).swim !== undefined; } function move(animal: Animal) { if (isFish(animal)) { // animal 被缩小为 Fish 类型 animal.swim(); } else { // animal 被缩小为 Bird 类型 animal.fly(); } } // 更实用的例子:API 响应类型保护 interface SuccessResponse { success: true; data: T; } interface ErrorResponse { success: false; error: string; } type ApiResponse = SuccessResponse | ErrorResponse; function isSuccess(response: ApiResponse): response is SuccessResponse { return response.success === true; } function handleResponse(response: ApiResponse) { if (isSuccess(response)) { console.log("Data:", response.data); // T 类型 } else { console.log("Error:", response.error); // string 类型 } } ==== 13.6.7 判别联合类型(Discriminated Unions)===== // 使用共同的判别属性 interface Circle { kind: "circle"; radius: number; } interface Square { kind: "square"; sideLength: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } type Shape = Circle | Square | Rectangle; // 使用判别属性进行类型缩小 function getArea(shape: Shape): number { switch (shape.kind) { case "circle": return Math.PI * shape.radius ** 2; // shape 是 Circle case "square": return shape.sideLength ** 2; // shape 是 Square case "rectangle": return shape.width * shape.height; // shape 是 Rectangle default: // 穷尽检查 const _exhaustiveCheck: never = shape; return _exhaustiveCheck; } } // 状态管理示例 type LoadingState = { status: "loading" }; type SuccessState = { status: "success"; data: string }; type ErrorState = { status: "error"; error: string }; type State = LoadingState | SuccessState | ErrorState; function renderState(state: State): string { switch (state.status) { case "loading": return "Loading..."; case "success": return `Data: ${state.data}`; // state 是 SuccessState case "error": return `Error: ${state.error}`; // state 是 ErrorState } } ===== 13.7 高级类型推断 ===== ==== 13.7.1 条件类型中的推断 ==== // 提取数组元素类型 type ElementType = T extends (infer E)[] ? E : T; type A = ElementType; // string type B = ElementType; // number[] type C = ElementType; // string // 提取函数返回类型 type ReturnType = T extends (...args: any[]) => infer R ? R : never; function getUser() { return { name: "Alice", age: 25 }; } type User = ReturnType; // { name: string; age: number } // 提取函数参数类型 type Parameters = T extends (...args: infer P) => any ? P : never; function updateUser(id: number, data: { name: string }) {} type UpdateUserParams = Parameters; // [number, { name: string }] ==== 13.7.2 模板字面量类型推断 ==== // 提取事件处理器名称 type EventName = `on${Capitalize}`; type ClickEvent = EventName<"click">; // "onClick" type HoverEvent = EventName<"hover">; // "onHover" // 从字符串中提取信息 type GetEvent = T extends `${infer Name}Changed` ? Name : never; type A = GetEvent<"nameChanged">; // "name" type B = GetEvent<"ageChanged">; // "age" type C = GetEvent<"foo">; // never ===== 13.8 本章小结 ===== 本章我们学习了: 1. **类型推断基础** - 编译器自动推导类型的机制 2. **let 与 const 的区别** - const 推断更具体的字面量类型 3. **函数类型推断** - 返回值和参数的推断规则 4. **上下文类型推断** - 根据赋值位置推断类型 5. **类型加宽** - 字面量类型扩展为一般类型 6. **类型缩小** - 通过条件检查缩小类型范围 7. **自定义类型保护** - is 关键字和类型保护函数 8. **判别联合类型** - 使用共同属性进行类型区分 ===== 13.9 练习题 ===== ==== 练习 1:基础类型推断 ==== 分析以下代码的类型推断结果: const name = "Alice"; let age = 25; const hobbies = ["reading", "coding"]; const person = { name: "Bob", age: 30 }; function greet(user: typeof person) { return `Hello, ${user.name}`; } const result = greet({ name: "Charlie", age: 35 });
参考答案 const name: "Alice" = "Alice"; // 字面量类型 let age: number = 25; // number 类型(加宽) const hobbies: string[] = [...]; // string[] 类型 const person: { name: string; age: number } = ...; // 对象类型 function greet(user: { name: string; age: number }): string { return `Hello, ${user.name}`; } const result: string = greet(...); ==== 练习 2:类型缩小应用 ==== 实现一个类型安全的日志函数,支持多种输入类型: function logMessage(message: string | Error | { code: number; msg: string }) { // 要求: // - 如果是 Error 类型,输出 error.message // - 如果是对象类型,输出 `[${code}] ${msg}` // - 如果是字符串,直接输出 } 参考答案 function logMessage(message: string | Error | { code: number; msg: string }) { if (message instanceof Error) { console.log(message.message); } else if (typeof message === "object") { console.log(`[${message.code}] ${message.msg}`); } else { console.log(message); } } // 测试 logMessage("Simple message"); logMessage(new Error("Something went wrong")); logMessage({ code: 404, msg: "Not found" }); ==== 练习 3:自定义类型保护 ==== 定义用户类型和类型保护函数: interface Admin { role: "admin"; permissions: string[]; } interface User { role: "user"; group: string; } type Person = Admin | User; // 实现类型保护函数 function isAdmin(person: Person): person is Admin { return person.role === "admin"; } // 使用示例 function checkPermission(person: Person, permission: string): boolean { if (isAdmin(person)) { return person.permissions.includes(permission); } return false; } ==== 练习 4:判别联合类型 ==== 实现一个网络请求状态管理: // 定义状态类型 // - Idle: 初始状态 // - Loading: 加载中,包含请求开始时间 // - Success: 成功,包含数据和完成时间 // - Error: 失败,包含错误信息和错误码 // 实现 renderStatus 函数,根据不同状态返回描述字符串 参考答案 interface IdleState { status: "idle"; } interface LoadingState { status: "loading"; startTime: Date; } interface SuccessState { status: "success"; data: T; completeTime: Date; } interface ErrorState { status: "error"; error: string; code: number; } type RequestState = | IdleState | LoadingState | SuccessState | ErrorState; function renderStatus(state: RequestState): string { switch (state.status) { case "idle": return "Ready to start"; case "loading": return `Loading since ${state.startTime.toISOString()}`; case "success": return `Completed at ${state.completeTime.toISOString()}: ${JSON.stringify(state.data)}`; case "error": return `[${state.code}] ${state.error}`; default: const _exhaustive: never = state; return _exhaustive; } } // 使用 const state: RequestState<{ users: number }> = { status: "success", data: { users: 100 }, completeTime: new Date() }; console.log(renderStatus(state)); ===== 扩展阅读 ===== - [[TypeScript:第十四章_类型操作符|下一章:类型操作符]] - [[https://www.typescriptlang.org/docs/handbook/type-inference.html|TypeScript 官方文档 - 类型推断]] - [[https://www.typescriptlang.org/docs/handbook/2/narrowing.html|TypeScript 官方文档 - 类型缩小]]