返回首页
·4 min read·编程语言

TypeScript 类型体操:从入门到实用

为什么需要高级类型

TypeScript 不只是给 JavaScript 加了类型标注。它的类型系统本身是图灵完备的,可以在编译期进行复杂的类型计算。合理使用高级类型,可以让错误在编译期暴露,减少运行时 Bug。

泛型:类型的参数

function identity<T>(value: T): T {
  return value;
}

// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

const user = { name: "Alice", age: 25 };
const name = getProperty(user, "name"); // 类型推断为 string

条件类型

条件类型让你可以基于类型关系做判断:

type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">; // true
type B = IsString<42>;      // false

infer 关键字

infer 可以在条件类型中提取类型变量:

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 提取 Promise 的值类型
type Awaited<T> = T extends Promise<infer U> ? U : T;

type Result = Awaited<Promise<string>>; // string

映射类型

映射类型可以基于一个类型生成新类型:

// 将所有属性变为可选
type Optional<T> = {
  [K in keyof T]?: T[K];
};

// 将所有属性变为只读
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

// 将所有属性变为可空
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

实用类型工具

Pick 与 Omit

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// 只取部分字段
type PublicUser = Pick<User, "id" | "name" | "email">;

// 排除部分字段
type SafeUser = Omit<User, "password">;

Record 与 Partial

// 字典类型
type RolePermissions = Record<"admin" | "editor" | "viewer", string[]>;

// 表单状态
type FormState = Partial<User>;

模板字面量类型

type EventName = "click" | "hover" | "focus";
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onHover" | "onFocus"

// API 路由类型
type ApiRoute = `/api/${string}`;

类型守卫

类型守卫帮助 TypeScript 收窄类型范围:

function process(value: string | number) {
  if (typeof value === "string") {
    return value.toUpperCase(); // 这里 value 是 string
  }
  return value.toFixed(2); // 这里 value 是 number
}

// 自定义类型守卫
function isApiError(error: unknown): error is { code: number; message: string } {
  return (
    typeof error === "object" &&
    error !== null &&
    "code" in error &&
    "message" in error
  );
}

实战:类型安全的 EventEmitter

class TypedEmitter<Events extends Record<string, any>> {
  private handlers: { [K in keyof Events]?: Array<(data: Events[K]) => void> } = {};

  on<K extends keyof Events>(event: K, handler: (data: Events[K]) => void) {
    (this.handlers[event] ??= []).push(handler);
  }

  emit<K extends keyof Events>(event: K, data: Events[K]) {
    this.handlers[event]?.forEach((h) => h(data));
  }
}

// 使用
interface MyEvents {
  login: { userId: string };
  logout: void;
}

const emitter = new TypedEmitter<MyEvents>();
emitter.on("login", (data) => {
  console.log(data.userId); // 类型安全!
});

总结

TypeScript 的类型系统是一门艺术。从泛型到条件类型,从映射类型到模板字面量类型——每一层都让你离"编译期消灭 Bug"更近一步。

原则:类型应该帮你减少错误,而不是增加心智负担。适度使用,过犹不及。

小楼春雨

@站长