TypeScript 的5个好用技巧

1,001 阅读5分钟

1. 使用 const 断言锁定类型,确保对象的属性不可更改。

可能你们在编码的时候也遇到过这样的情况,我们定义的接口类型被莫名其妙的修改了导致代码的报错,这样的情况会浪费我们很多不必要的时间来排查bug。那我们应该如何杜绝这种情况的发生呢,这时 const 断言就派上用场了!使用 as const 后,TypeScript 可以确保我们定义的接口类型在后续代码中不会发生变化。这就像为变量加上“请勿触摸”的标志一样,以确保其安全。

const colors = ['red', 'green', 'blue'] as const;

// 没有 `as const` 时,类型是 `string[]`
let colors1 = ['red', 'green', 'blue'];
// colors1[0] = 'yellow'; // 允许

// 使用 `as const` 后,类型是 `readonly ["red", "green", "blue"]`
const colors2 = ['red', 'green', 'blue'] as const;
// colors2[0] = 'yellow'; // 错误,不能修改数组的元素

当我们使用 as const 时,TypeScript 会进行以下操作:

  1. 将数组或对象中的每个元素或属性标记为 readonly:

    • 对于数组,使用 as const 后,每个元素都会变为只读,数组本身也会变为只读。
    • 对于对象,使用 as const 后,每个属性都会变为只读。
  2. 推断出最具体的字面量类型:

    • 不使用 as const 时,TypeScript 通常会推断出更宽泛的类型。
    • 使用 as const 后,TypeScript 会推断出最具体的字面量类型。

2. 利用 Pick 创建自定义类型,从大型类型中选择需要的部分。

假如我们已经定义过了一个拥有很多类型的A接口,但是我们在后续的代码操作中还需求一个新的B接口,但是新的B接口里的类型已经在之前的A接口里定义过一次了,(A>B),我们的B接口只需要A接口的其中的一类型部分。

这时我们就可以使用 Pick 来帮助我们实现,减少代码里的重复定义。我们可以创建一个仅选择所需内容的新接口。就像我们在超市买散装糖果有一些,我们只需挑选我们想要吃的口味的糖果就行,而无需为了某种口味,而整包购买。

interface User { // 定义一个接口 User
  id: number; // 用户 ID
  name: string; // 用户名
  email: string; // 用户邮箱
}

type UserSummary = Pick<User, 'name' | 'email'>; // 使用 Pick 从 User 类型中选择 'name' 和 'email' 属性,创建新类型 UserSummary

const user: User = { // 定义一个 User 类型的对象 user
  id: 1,
  name: 'John Doe',
  email: 'john.doe@example.com'
};

const summary: UserSummary = { // 定义一个 UserSummary 类型的对象 summary
  name: user.name, // 从 user 对象中获取 name 属性
  email: user.email // 从 user 对象中获取 email 属性
};

3. 使用 Extract 缩小选项范围,从联合类型中提取特定的选项。

当我们已经拥有一个联合类型了,而我们又需要创建一个被联合类型所包含的类型时,Extract就起作用了。

Extract的作用就是从一个联合类型(union type)中提取符合指定条件的子类型

type A = string | number | boolean;
type B = Extract<A, string | number>; // B 是 string | number


type Animal = 'dog' | 'cat' | 'bird' | 'fish'; 
type Mammal = Extract<Animal, 'dog' | 'cat'>;

在上面这个例子中,A 是一个联合类型 string | number | boolean。我们使用 Extract<A, string | number> 提取出可以赋值给B的子类型,结果是 B 的类型就是string | number。

从接口类型中提取

interface Person {
    name: string;
    age: number;
    isEmployee: boolean;
}

type StringKeys = Extract<keyof Person, string>; // StringKeys 是 'name'

//在这个例子中,我们使用 `keyof Person` 获取 `Person` 接口的所有键,然后用 `Extract` 提取出键为 `string` 类型的键,结果是 `'name'`。

提取特定类型的函数

type Func = (x: number) => string | ((x: string) => number) | ((x: boolean) => void);
type StringToNumberFunc = Extract<Func, (x: string) => any>; 
// StringToNumberFunc 是 (x: string) => number

4. 使用 Readonly 保障数据安全,确保数据或对象的内容不可更改。

在没有使用TypeScript之前,我们的要把变量只读都是通过使用Object.defineProperty()函数将obj对象的pro1属性修改为不可写。这样就实现了只读变量。但是使用TypeScript之后,我们想要声明一个只读变量变的十分简单。只要使用Readonly就行。

const fruits: ReadonlyArray<string> = ['apple', 'banana', 'cherry']; // 定义一个 ReadonlyArray 类型的数组 fruits,不可修改

// 这将导致 TypeScript 错误
// fruits.push('date'); // 尝试向 ReadonlyArray 添加新元素,导致 TypeScript 错误

// 这也将导致 TypeScript 错误
// fruits[1] = 'blueberry'; // 尝试修改 ReadonlyArray 中的元素,导致 TypeScript 错误

5. 使用keyof获取对象的所有类型,获取类型的所有键

在开发时,我们可能也会遇到需要获取一个对象的所有类型的情景,这时我们就可以使keyof来获取。

interface User { // 定义一个接口 User
  id: number; // 用户 ID
  name: string; // 用户名
  email: string; // 用户邮箱
}

type UserKey = keyof User; // 使用 keyof 获取 User 类型的所有键,创建新类型 UserKey
//PersonKeys 是一个联合类型,它包含了 Person 类型的所有键

const key: UserKey = 'name'; // 定义一个 UserKey 类型的变量 key,赋值为 'name'
// 这将导致 TypeScript 错误
// const invalidKey: UserKey = 'age'; // 尝试将不存在于 User 类型中的键 'age' 赋值给 UserKey 类型的变量,导致 TypeScript 错误

假设我们想要创建一个类型,并且我们想把 User 类型中的所有属性都变成可选的,这时我们也可以使用keyof

type PartialPerson = {
  [K in keyof User]?: User[K];
};

或者又是我们想创建一个类型,来提取对象中所有值类型为字符串的属性键

// 定义一个泛型类型 StringKeys,它接受一个类型参数 T
type StringKeys<T> = {
  // 遍历 T 的所有键 K,并生成一个新的类型
  [K in keyof T]: 
    // 检查 T[K] 是否为 string 类型
    T[K] extends string 
      // 如果是,则返回键 K
      ? K 
      // 如果不是,则返回 never
      : never
  // 取出所有键的联合类型
}[keyof T];

// 使用 StringKeys 类型来提取 Person 类型中所有值为 string 的键
type PersonStringKeys = StringKeys<Person>; // "name" | "address"