TypeScript: утилитарные типы

Утилитарные типы — это ряд встроенных продвинутых типов в TypeScript.

Они помогают разработчикам быстро создавать и преобразовывать типы, повышая переиспользуемость кода и типобезопасность.

Утилитарные типы по сути являются обобщёнными типами, реализованными через отображаемые типы и условные типы.


Background Title TypeScript Utility Types Primitive Types Primitive Types interface User { id: number; name: string; email: string; } Arrow Utility Types Utility Types Partial<T> - All Optional Required<T> - All Required Readonly<T> - All Readonly Pick<T,K> - Select Properties Omit<T,K> - Exclude Properties Arrow Result Result Type type Result = { id?: number; name?: string; email?: string; } Lower Part Common Utility Types First Row Partial<T> All properties optional { a?: T } Required<T> All properties required { -? } Readonly<T> All properties readonly { readonly } Pick<T,K> Select specified properties { pick } Second Row (Fix Focus) Record<K,T> Construct Object Types Omit<T,K> Exclude Specified Properties Arrow Definition

Зачем нужны утилитарные типы?

В реальной разработке на TypeScript нам часто нужно создавать новые типы на основе существующих типов.

Ручное создание таких типов не только утомительно, но и подвержено ошибкам.

Утилитарные типы предоставляют декларативный способ преобразования и создания типов, что значительно повышает эффективность разработки.

Объяснение концепции:Утилитарные типы — это серия предопределённых обобщённых типов в TypeScript. Они используют отображаемые типы и условные типы для преобразования и генерации типов.


Partial<T> - Необязательные свойства

Partial делает все свойства типа T необязательными.

Это очень полезно при создании частично обновлённых объектов или обработке данных формы.

Пример

// Определить интерфейс пользователя с обязательными свойствами
interface User {
    // ID пользователя
    id: number;
    // Имя пользователя
    name: string;
    // Электронная почта пользователя
    email: string;
}

// Partial: делает все свойства необязательными
// Все свойства в преобразованном типе являются необязательными
type PartialUser = Partial<User>;

// Использовать тип PartialUser
// Вы можете указать только некоторые свойства, а не все
var user: PartialUser = { name: "Alice" };

console.log("Частичный пользователь: " + JSON.stringify(user));

Результат выполнения:

部分用户: {"name":"Alice"}

Сценарии применения:Partial часто используется для обновления данных объекта. Например: при обновлении только части информации о пользователе не нужно передавать весь объект пользователя.


Required<T> — обязательные свойства

Required — противоположность Partial; он делает все необязательные свойства обязательными.

Используйте его, когда нужно гарантировать, что объект содержит все свойства.

Пример

// Определите интерфейс config со всеми необязательными свойствами
interface Config {
    // Адрес сервера
    host?: string;
    // Номер порта
    port?: number;
}

// Required: делает все необязательные свойства обязательными
// Все свойства в преобразованном типе являются обязательными
type RequiredConfig = Required<Config>;

// Используйте тип RequiredConfig
// Необходимо указать все свойства
var config: RequiredConfig = { host: "localhost", port: 8080 };

console.log("Config: " + JSON.stringify(config));

Результат выполнения:

配置: {"host":"localhost","port":8080}

Примечание:Required не только удаляет маркер необязательности (?), но также удаляет модификатор readonly.


Readonly<T> - Свойства только для чтения

Readonly устанавливает для всех свойств модификатор readonly.

Это очень полезно при создании объектов, которые нельзя изменить после создания.

Пример

// Определяем интерфейс пользователя
interface User {
    // Имя пользователя
    name: string;
    // Возраст пользователя
    age: number;
}

// Readonly: делает все свойства доступными только для чтения
// Ни одно из свойств в преобразованном типе нельзя изменить
type ReadonlyUser = Readonly<User>;

// Создать объект пользователя, доступный только для чтения
var user: ReadonlyUser = { name: "Alice", age: 25 };

// Попытка изменить свойство readonly вызовет ошибку
// user.name = "Bob"; // Ошибка: свойство readonly нельзя изменить

console.log("Пользователь только для чтения: " + JSON.stringify(user));

Сценарии применения:Readonly часто используется для определения объектов конфигурации, сопоставлений значений перечислений и других данных, которые не должны изменяться.


Pick<T, K> — Выбор свойств

Pick выбирает указанные свойства K из типа T для формирования нового типа.

Используйте его, когда вам нужны только некоторые свойства типа.

Пример

// Определить полный интерфейс пользователя
interface User {
    // ID пользователя
    id: number;
    // Имя пользователя
    name: string;
    // Электронная почта пользователя
    email: string;
    // Пароль пользователя
    password: string;
}

// Pick: выбрать указанные свойства для формирования нового типа
// Выбрать свойства id и name из User
type UserBasicInfo = Pick<User, "id" | "name">;

// Использовать выбранный тип
var user: UserBasicInfo = { id: 1, name: "Alice" };

console.log("Основная информация о пользователе: " + JSON.stringify(user));

Результат выполнения:

用户基本信息: {"id":1,"name":"Alice"}

Сравнение:Объединение Pick и Partial позволяет создать тип, содержащий только некоторые необязательные свойства.


Omit<T, K> - Исключение свойств

Omit исключает указанные свойства K из типа T и возвращает новый тип, состоящий из оставшихся свойств.

Это противоположность Pick и используется для удаления ненужных свойств.

Пример

// Определить полный интерфейс пользователя
interface User {
    // ID пользователя
    id: number;
    // Имя пользователя
    name: string;
    // Электронная почта пользователя
    email: string;
    // Пароль пользователя
    password: string;
}

// Omit: исключить указанные свойства
// Исключить свойство password из User
type UserWithoutPassword = Omit<User, "password">;

// Использовать результирующий тип после исключения
var user: UserWithoutPassword = { id: 1, name: "Alice", email: "[email protected]" };

console.log("Пользователь без пароля: " + JSON.stringify(user));

Результат выполнения:

无密码用户: {"id":1,"name":"Alice","email":"[email protected]"}

Совет:Omit — обратная операция к Pick. Когда нужно исключить несколько свойств, используйте Omit; когда нужно выбрать несколько свойств, используйте Pick.


Record<K, T> - Создание типов объектов

Record создаёт тип объекта с ключами типа K и значениями типа T.

Он часто используется для создания отображений ключ-значение, словарных типов и т. д.

Пример

// Определить тип роли
type Role = "admin" | "user" | "guest";

// Record: создаёт тип объекта
// Ключи имеют тип Role, значения — массив строк
type RolePermissions = Record<Role, string[]>;

// Используйте Record для создания сопоставления разрешений
var permissions: RolePermissions = {
    // Администратор имеет все разрешения
    admin: ["read", "write", "delete"],
    // Обычный пользователь имеет разрешения на чтение и запись
    user: ["read", "write"],
    // Гость имеет только разрешение на чтение
    guest: ["read"]
};

console.log("Права администратора: " + permissions.admin);
console.log("Права гостя: " + permissions.guest);

Результат выполнения:

管理员权限: read,write,delete
访客权限: read

Сценарии применения:Record часто используется в сценариях, где значения нужно быстро находить по ключу, например в сопоставлениях конфигурации, состояния и разрешений.


Exclude<T, U> — исключение типов

Exclude удаляет из типа T все типы, которые можно присвоить типу U.

В основном используется для объединённых типов, исключая конкретные члены типа.

Пример

// Определить объединённый тип, содержащий a, b, c, d
type T = "a" | "b" | "c" | "d";

// Exclude: удалить указанные типы из T
// Exclude "a", "b", "c", оставив только "d"
type NonABC = Exclude<T, "a" | "b" | "c">;

// Используя исключённый тип, можно присвоить только "d"
var value: NonABC = "d";

console.log("Значение: " + value);

Вывод:

值: d

Пояснение:Принцип Exclude таков: если тип из T может быть присвоен U, удалите его из T.


Extract<T, U> - Извлечение типов

Extract — противоположность Exclude; он извлекает из типа T типы, которые могут быть присвоены типу U.

Используется для фильтрации определённых членов из union-типа.

Пример

// Определите смешанный union-тип, содержащий строки и числа
type T = "a" | "b" | "c" | 1 | 2 | 3;

// Extract: извлечь указанные типы из T
// Извлечь все строковые типы: "a", "b", "c"
type Letters = Extract<T, string>;

// Используйте извлечённый тип
var letter: Letters = "a";

console.log("Буквы: " + letter);

Вывод:

字母: a

Сравнение:Extract и Exclude дополняют друг друга. Extract(T, U) эквивалентно Exclude(T, Exclude<T, U>).


NonNullable<T> - Исключение nullish-значений

NonNullable удаляет null и undefined из типа T.

Используйте его, чтобы гарантировать, что тип не содержит nullish-значений.

Пример

// Определите смешанный тип, содержащий строки, null, undefined и числа
type T = string | null | undefined | число;

// NonNullable: удалить null и undefined
// После преобразования остаются только string и number
type NotNull = NonNullable<T>;

// Используйте non-nullable тип
var value: NotNull = "hello";
value = 42;

// Присваивание null вызовет ошибку
// value = null; // Ошибка: нельзя присвоить null

console.log("Значение: " + value);

Объяснение:NonNullable<T> эквивалентен Exclude<T, null | undefined>.


ReturnType<T> — получение типа возвращаемого значения

ReturnType получает тип возвращаемого значения функционального типа T.

Часто используется для извлечения типа возвращаемого значения из существующей функции.

Пример

// Определить функцию для получения пользователя
function getUser() {
    return { name: "Alice", age: 25 };
}

// Определить функцию для получения конфигурации
function getConfig() {
    return { host: "localhost", port: 8080 };
}

// ReturnType: получить тип возвращаемого значения функции
// Извлечь тип возвращаемого значения функции getUser
type UserType = ReturnType<typeof getUser>;
// Извлеките тип возвращаемого значения функции getConfig
type ConfigType = ReturnType<typeof getConfig>;

// Используйте извлечённые типы возвращаемых значений для создания объектов
var user: UserType = { name: "Bob", age: 30 };
var config: ConfigType = { host: "example.com", port: 3000 };

console.log("Пользователь: " + JSON.stringify(user));
console.log("Config: " + JSON.stringify(config));

Вывод:

User: {"name":"Bob","age":30}
配置: {"host":"example.com","port":3000}

Совет:T в ReturnType<T> должен быть функциональным типом. Вы можете использовать typeof, чтобы получить тип функции.


Примечания

  • Все служебные типы являются обобщёнными:При их использовании передавайте конкретные аргументы типа
  • Readonly и необязательные:Служебные типы можно комбинировать, например, Partial<Readonly<T>>
  • Встроенные типы:Эти служебные типы встроены в TypeScript и не требуют установки
  • Пользовательские служебные типы:Вы можете создавать собственные служебные типы на основе отображаемых типов и условных типов

Для продвинутых:Если встроенные служебные типы не удовлетворяют вашим потребностям, вы можете реализовать собственные служебные типы, обратившись к исходному коду TypeScript.


Итог

Служебные типы TypeScript — важная часть системы типов.

  • Partial<T>:Сделать все свойства необязательными
  • Required<T>:Сделать все свойства обязательными
  • Readonly<T>:Сделать все свойства доступными только для чтения
  • Pick<T,K>:Выбрать указанные свойства
  • Omit<T,K>:Исключить указанные свойства
  • Record<K,T>:Создать типы объектов
  • Exclude/Extract:Фильтрация типов
  • NonNullable:Исключить null и undefined
  • ReturnType:Получить тип возвращаемого значения функции

Лучшие практики:Умелое использование служебных типов может сделать код более типобезопасным, сократить повторяющиеся определения типов и повысить удобство сопровождения кода.


Другие расширения