TypeScript: утилитарные типы
Утилитарные типы — это ряд встроенных продвинутых типов в TypeScript.
Они помогают разработчикам быстро создавать и преобразовывать типы, повышая переиспользуемость кода и типобезопасность.
Утилитарные типы по сути являются обобщёнными типами, реализованными через отображаемые типы и условные типы.
Зачем нужны утилитарные типы?
В реальной разработке на 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; он делает все необязательные свойства обязательными.
Используйте его, когда нужно гарантировать, что объект содержит все свойства.
Пример
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.
В основном используется для объединённых типов, исключая конкретные члены типа.
Пример
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-типа.
Пример
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-значений.
Пример
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:Получить тип возвращаемого значения функции
Лучшие практики:Умелое использование служебных типов может сделать код более типобезопасным, сократить повторяющиеся определения типов и повысить удобство сопровождения кода.
Другие расширения