Utility Types in TypeScript

TypeScript 提供了一些內建的 Utility Types,可以幫助我們更方便地操作型別。

Utility Types

Pick

Pick Utility Type 可以從一個 Interface 中選擇一些屬性,形成一個新的 Interface。

當你只需要某個 Interface 的部分屬性時,可以使用 Pick Utility Type。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

// 只選擇 description 和 completed 屬性
type TodoPreview = Pick<Todo, "description" | "completed">;

function updateTodo(todo: Todo, fieldsToUpdate: TodoPreview) {
  return {
    ...todo,
    ...fieldsToUpdate,
  };
}

const todo1 = updateTodo(
  {
    title: "Old Title",
    description: "Old Description",
    completed: false,
  },
  // 只需要傳入 description 和 completed 屬性
  {
    description: "New Description",
    completed: true,
  },
);

console.log(todo1);

Omit

Omit Utility Type 與 Pick Utility Type 相反,可以從一個 Interface 中排除一些屬性,形成一個新的 Interface。

當你只需要某個 Interface 的部分屬性時,可以使用 Omit Utility Type。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

// 排除 description 和 completed 屬性
type TodoPreview = Omit<Todo, "description" | "completed">;

function updateTodo(todo: Todo, fieldsToUpdate: TodoPreview) {
  return {
    ...todo,
    ...fieldsToUpdate,
  };
}

const todo1 = updateTodo(
  {
    title: "Old Title",
    description: "Old Description",
    completed: false,
  },
  // 只需要傳入 title 屬性
  {
    title: "New Title",
  },
);

console.log(todo1);

Partial

Partial Utility Type 可以將一個 Interface 的所有屬性都轉換為可選的 (optional)。

當你只需要某個 Interface 的部分屬性時,可以使用 Partial Utility Type。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

// 將所有屬性都轉換為可選的
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return {
    ...todo,
    ...fieldsToUpdate,
  };
}

const todo1 = updateTodo(
  {
    title: "Old Title",
    description: "Old Description",
    completed: false,
  },
  // 只需要傳入 title 和 description 屬性
  {
    title: "New Title",
    description: "New Description",
  },
);

console.log(todo1);

Required

Required Utility Type 可以將一個 Interface 的所有屬性都轉換為必填的 (required)。

Interface 有些屬性可能為可選的 (optional),使用 Required Utility Type 可以將這些可選的屬性轉換為必填的。

interface Todo {
  title: string;
  // description 是可選的
  description?: string;
  completed: boolean;
}

// 將所有屬性都轉換為必填的
function updateTodo(todo: Todo, fieldsToUpdate: Required<Todo>) {
  return {
    ...todo,
    ...fieldsToUpdate,
  };
}

const todo1 = updateTodo(
  {
    title: "Old Title",
    description: "Old Description",
    completed: false,
  },
  // 只需要傳入 title 和 description 屬性
  {
    title: "New Title",
    // description 變成必填的
    description: "New Description",
    completed: true,
  },
);

console.log(todo1);

Readonly

Readonly Utility Type 可以將一個 Interface 的所有屬性都轉換為只讀的 (readonly)。

當你只需要某個 Interface 的部分屬性時,可以使用 Readonly Utility Type。

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}

type T = Readonly<Todo>;

const todo1: T = {
  title: "Old Title",
  description: "Old Description",
  completed: false,
};

// 下面這行會出現錯誤,因為 title 是只讀的
// Cannot assign to 'title' because it is a read-only property.(2540)
todo1.title = "New Title";

Record

Record Utility Type

type User = {
  id: string;
  name: string;
  age: number;
};

// 建立一個 key-value pair 的 type,key 是 string,value 是 User
type T = Record<string, User>;

const a: T = {
  "1_allen": {
    id: "1",
    name: "Allen",
    age: 25,
  },
  "2_bob": {
    id: "2",
    name: "Bob",
    age: 30,
  },
};

// 建立一個 key-value pair 的 type,key 是 "admin" 或 "user",value 是 User
type U = Record<"admin" | "user", User>;

const b: U = {
  admin: {
    id: "1",
    name: "Allen",
    age: 25,
  },
  user: {
    id: "2",
    name: "Bob",
    age: 30,
  },
};

Extract

可以從 Type 中提取出符合條件的 Type。

type Role = "admin" | "user" | "guest";
type Permission = "read" | "write" | "delete";
type OtherRole = "testing" | "admin" | "user" | "guest" | "moderator";

type AdminRole = Extract<Role, "admin">;
type UserPermission = Extract<Permission, "read" | "write">;
// 類似交集,只提取 Role 在 OtherRole 中的共通部分
type T = Extract<Role, OtherRole>;

Exclude

可以從 Type 中排除出不符合條件的 Type。

type Role = "admin" | "user" | "guest";
type Permission = "read" | "write" | "delete";
type OtherRole = "testing" | "admin" | "user" | "guest" | "moderator";

type AdminRole = Exclude<Role, "admin">;
type UserPermission = Exclude<Permission, "read" | "write">;
// 類似差集,排除 Role 在 OtherRole 中的共通部分
type T = Exclude<Role, OtherRole>;

ReturnType

ReturnType Utility Type 可以從一個 Function Type 中提取出返回值的 Type。

function getUser() {
  return { id: 1, name: "Allen", age: 25 };
}

type T = ReturnType<typeof getUser>;

const user: T = {
  id: 1,
  name: "Allen",
  age: 25,
};

Parameters

Parameters Utility Type 可以從一個 Function Type 中提取出參數的 Type。

當你有一個函式的參數想參照其他函式的參數時,可以使用 Parameters Utility Type。

function getUser(id: number) {
  return { id, name: "Allen", age: 25 };
}

type T = Parameters<typeof getUser>;

const id: T = [1];

ConstructorParameters

ConstructorParameters Utility Type 可以從一個 Class 中提取出建構子的參數的 Type。

class User {
  constructor(
    public id: number,
    public name: string,
    public age: number,
  ) {}
}

type T = ConstructorParameters<typeof User>;

const user: T = [1, "Allen", 25];

InstanceType

有點莫名其妙的 Utility Type

InstanceType Utility Type 可以從一個 Class 中提取出建構子的返回值的 Type。

class User {
  constructor(
    public id: number,
    public name: string,
    public age: number,
  ) {}
}

type T = InstanceType<typeof User>;
// 上面這一句等同於下面這一句,所以 InstanceType 是用途有點微妙的 Utility Type
type T = User;

NonNullable

NonNullable Utility Type 可以從一個 Type 中排除出 null 和 undefined 的 Type。

type A = string | number | null | undefined;

// T 會排除掉 null 和 undefined,只剩下 string | number
type T = NonNullable<A>;

Awaited

Awaited Utility Type 可以從一個 Promise Type 中提取出返回值的 Type。

function getUser() {
  return Promise.resolve({ id: 1, name: "Allen", age: 25 });
}

// T 會提取出 Promise 的返回值的 Type
// type T = { id: number; name: string; age: number }
type T = Awaited<ReturnType<typeof getUser>>;

字串相關的 Utility Type

type T = Uppercase<"hello">; // "HELLO"
type T = Lowercase<"HELLO">; // "hello"
type T = Capitalize<"hello">; // "Hello"
type T = Uncapitalize<"Hello">; // "hello"

參考資料

  • You are a Junior Dev if You Don’t Know These 18 TypeScript Utility Types

This site uses Just the Docs, a documentation theme for Jekyll.