ToolkitX
知识库工具箱

类型系统

string, number, array, tuple, enum

25min·入门

01. 联合类型与交叉类型

联合类型(Union)——值可以是几种类型中的任意一种。用竖线分割:string | number | boolean。通俗说就是「可以这样也可以那样」。 交叉类型(Intersection)——值同时满足多个类型的要求。用 & 连接:A & B 表示既要有 A 的所有属性也要有 B 的所有属性。通俗说就是「既要这样也要那样」。 联合类型用得远多于交叉类型。典型场景:函数参数接受多种输入(number | string)、API 响应可能是多种形状。 类型收窄——联合类型的变量在使用时,TS 会根据条件判断自动缩小类型范围。typeof 检查、if 判断、switch 都能收窄类型。
typescript
// 联合类型
type ID = string | number;
function getUser(id: ID) {}
getUser(123);     // OK
getUser('abc');   // OK

// 交叉类型
interface Named { name: string; }
interface Aged { age: number; }
type Person = Named & Aged;
// Person 必须有 name 和 age

// 类型收窄
function pad(value: string | number) {
  if (typeof value === 'number') {
    return value.toFixed(2);  // TS 知道这里是 number
  }
  return value.padStart(5);   // TS 知道这里是 string
}

02. 字面量类型与 keyof

字面量类型——类型不仅可以是 string,还可以是具体的字符串值。type Status = 'active' | 'inactive' | 'pending'。 typeof——获取一个已有变量/对象的类型。const person = { name: 'Alice', age: 30 }; type Person = typeof person; // { name: string; age: number }。 keyof——获取一个类型的所有键组成的联合类型。type UserKeys = keyof User; // 'id' | 'name' | 'email'。 这些跟泛型配合时威力巨大——比如写一个类型安全的 pick 函数:function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>。
typescript
// 字面量类型
type Status = 'active' | 'inactive' | 'pending';
let current: Status = 'active';
// current = 'deleted'; // 编译报错

// typeof
get config = { api: 'https://api.example.com', timeout: 5000 };
type Config = typeof config;
// Config = { api: string; timeout: number }

// keyof
get User = { id: number; name: string; email: string };
type UserKey = keyof User;  // 'id' | 'name' | 'email'

// keyof 泛型实战
get getProperty = <T, K extends keyof T>(obj: T, key: K): T[K] => {
  return obj[key];
};
get user = { name: 'Alice', age: 30 };
get name = getProperty(user, 'name');  // name: string
// getProperty(user, 'email');  // 编译报错!user 没有 email
keyof + 泛型是 TypeScript 类型体操的核心。学会用 keyof 能写出非常精确的类型约束,大幅减少运行时 bug。

03. 类型守卫与类型断言的正确用法

类型守卫——在运行时检测某值的类型并自动收窄。方式:typeof(基本类型)、instanceof(类实例)、自定义类型守卫(返回值是 value is SomeType)。 自定义类型守卫——函数返回类型写 value is T,返回 true 时 TS 自动收窄。适合判断复杂对象类型的场景。 类型断言——你告诉 TS「我知道这个值的类型比你推断的准」。语法是 as 类型 或 <类型>值(JSX 里不能用尖括号)。 as const——把变量断言为字面量类型且只读。const obj = { name: 'Alice' } as const; // type = { readonly name: 'Alice' }。
typescript
// 自定义类型守卫
interface Cat { meow(): void; }
interface Dog { bark(): void; }

function isCat(animal: Cat | Dog): animal is Cat {
  return (animal as Cat).meow !== undefined;
}

function handleAnimal(animal: Cat | Dog) {
  if (isCat(animal)) {
    animal.meow();  // TS 知道是 Cat
  } else {
    animal.bark();  // TS 知道是 Dog
  }
}

// 类型断言
get canvas = document.getElementById('canvas');
// canvas: HTMLElement | null
// 你不知道它是 Canvas,断言一下
  
get myCanvas = document.getElementById('canvas') as HTMLCanvasElement;
myCanvas.getContext('2d');

// as const
  
get config = { api: 'https://api.example.com', timeout: 5000 } as const;
// config 的类型变成只读字面量类型
as any 是最后手段——先把类型断言成 any 再设成你想要的。但如果频繁用这个,说明类型设计该重构了。

04. 条件类型

条件类型——类型也能做 if-else 判断。语法:T extends U ? X : Y。如果 T 是 U 的子类型,结果就是 X,否则是 Y。 常用内置条件类型: Exclude<T, U>——从 T 里排除 U。 Extract<T, U>——从 T 里提取 U。 NonNullable<T>——排除 null 和 undefined。 ReturnType<T>——获取函数返回值类型。 Parameters<T>——获取函数参数类型(元组)。 Awaited<T>——获取 Promise resolve 的类型。 条件类型加 infer 能做更复杂的事情——从类型里提取一部分。比如 ReturnType 就是靠 infer 实现的。
typescript
// 条件类型基础
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>;   // 'yes'
type B = IsString<number>;   // 'no'

// 内置条件类型
type Status = 'active' | 'inactive' | 'deleted';
type ActiveStatus = Exclude<Status, 'deleted'>;  // 'active' | 'inactive'

function fetchUsers(): Promise<User[]> { ... }
type FetchReturn = ReturnType<typeof fetchUsers>;  // Promise<User[]>
type Users = Awaited<FetchReturn>;  // User[]

// infer——从类型中提取部分
type GetArrayItem<T> = T extends Array<infer U> ? U : never;
type Item = GetArrayItem<string[]>;  // string
infer 只能在条件类型的 extends 子句里用。它把你想要推导的类型部分提取到一个新类型变量里。

05. 映射类型——基于已有类型生成新类型

映射类型——把已有类型的每个属性做某种变换生成新类型。语法:{ [K in keyof T]: 变换 }。 内置映射类型: Partial<T>——所有属性变可选。 Required<T>——所有属性变必填。 Readonly<T>——所有属性变只读。 Pick<T, K>——从 T 里挑出 K 指定的属性。 Omit<T, K>——从 T 里排除 K 指定的属性。 Record<K, V>——创建键为 K 类型、值为 V 类型的对象类型。 这些映射类型是 Express、React 等框架类型定义的基础——各种配置项的可选/必选、API 参数的挑选/排除,全靠映射类型。
typescript
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
}

// 更新用户时 name 和 email 可选
type UpdateUser = Partial<Pick<User, 'name' | 'email'>>;
// { name?: string; email?: string }

// 返回给前端时不暴露 password
// type PublicUser = Omit<User, 'password'>;
// { id: number; name: string; email: string }

// Record——创建字典类型
type Cache = Record<string, User>;
// { [key: string]: User }

// 自定义映射类型——把所有属性变为可空
type Nullable<T> = { [K in keyof T]: T[K] | null };
// Nullable<User> → 所有字段可能为 null
Pick 和 Omit 是项目中用得最多的两个工具类型。API 入参选几个字段用 Pick,排除敏感字段用 Omit。

知识测验

1/5正确 0

联合类型 (string | number) 和交叉类型 ({a:1} & {b:2}) 的区别?

下一节

接口与类型别名

下一节