第十三章:类型推断
本章概述
类型推断(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<T> {
success: true;
data: T;
}
interface ErrorResponse {
success: false;
error: string;
}
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;
function isSuccess<T>(response: ApiResponse<T>): response is SuccessResponse<T> {
return response.success === true;
}
function handleResponse<T>(response: ApiResponse<T>) {
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> = T extends (infer E)[] ? E : T;
type A = ElementType<string[]>; // string
type B = ElementType<number[][]>; // number[]
type C = ElementType<string>; // string
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
return { name: "Alice", age: 25 };
}
type User = ReturnType<typeof getUser>;
// { name: string; age: number }
// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function updateUser(id: number, data: { name: string }) {}
type UpdateUserParams = Parameters<typeof updateUser>;
// [number, { name: string }]
13.7.2 模板字面量类型推断
// 提取事件处理器名称
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type HoverEvent = EventName<"hover">; // "onHover"
// 从字符串中提取信息
type GetEvent<T extends string> =
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 });
<details> <summary>参考答案</summary>
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<T> {
status: "success";
data: T;
completeTime: Date;
}
interface ErrorState {
status: "error";
error: string;
code: number;
}
type RequestState<T> =
| IdleState
| LoadingState
| SuccessState<T>
| ErrorState;
function renderStatus<T>(state: RequestState<T>): 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));