typescript:第十三章_类型推断

第十三章:类型推断

类型推断(Type Inference)是 TypeScript 的核心特性之一,它允许编译器在没有显式类型注解的情况下自动推断出变量的类型。本章将深入探讨类型推断的工作原理、上下文类型推断、类型加宽(Widening)与类型缩小(Narrowing)等重要概念,帮助你编写既简洁又类型安全的代码。

类型推断是指 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'

从初始化值推断:

// 字符串
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 }

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 - 不能重新赋值
// 数组解构
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
// 简单返回值推断
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 }
}
// 基本箭头函数
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

上下文类型(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);
};
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}`;
  }
);
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: 类型不匹配
];

类型加宽是指 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
// 对象字面量 - 属性类型会加宽
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"]
// 使用显式类型阻止加宽
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 };
// 保持具体类型,同时检查是否符合接口
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");
  }
}
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);
}
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!");
  }
}
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();
  }
}
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}`);
  }
}
// 定义类型
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 类型
  }
}
// 使用共同的判别属性
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
  }
}
// 提取数组元素类型
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 }]
// 提取事件处理器名称
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

本章我们学习了:

1. 类型推断基础 - 编译器自动推导类型的机制

2. let 与 const 的区别 - const 推断更具体的字面量类型

3. 函数类型推断 - 返回值和参数的推断规则

4. 上下文类型推断 - 根据赋值位置推断类型

5. 类型加宽 - 字面量类型扩展为一般类型

6. 类型缩小 - 通过条件检查缩小类型范围

7. 自定义类型保护 - is 关键字和类型保护函数

8. 判别联合类型 - 使用共同属性进行类型区分

分析以下代码的类型推断结果:

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(...);

实现一个类型安全的日志函数,支持多种输入类型:

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" });

定义用户类型和类型保护函数:

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;
}

实现一个网络请求状态管理:

// 定义状态类型
// - 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));

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • typescript/第十三章_类型推断.txt
  • 最后更改: 2026/03/09 15:25
  • 张叶安