TypeScript: отображаемые типы

Сопоставленные типы — это мощная возможность в TypeScript для создания новых типов на основе существующих типов.

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

Сопоставленные типы — это ключевая технология для реализации встроенных служебных типов TypeScript.


SVG-диаграмма: как работают сопоставленные типы Background Title How Mapped Types Work Original Type Original Type interface User { id: number; name: string; } Arrow Mapping syntax Mapping syntax { [P in keyof T]?: T[P] // Add optional modifier } Arrow Result Type Result Type type PartialUser = { id?: number; name?: string; } Lower section: Modifier description Mapped Type Modifiers Modifier Description ? prefix Add optional modifier type Partial<T> readonly prefix Add readonly modifier type Readonly<T> -? prefix Remove optional modifier type Required<T> as keyword Remap key names as `${P}` Arrow marker

Зачем нужны сопоставленные типы

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

Например, версию, где все свойства необязательны, или версию, где все свойства readonly.

Традиционный способ — определять эти типы вручную, что утомительно и чревато ошибками.

Сопоставленные типы предоставляют декларативный способ автоматического создания таких вариантов типов.

Объяснение концепции:Сопоставленные типы используютkeyofиinключевое слово для перебора всех ключей существующего типа с последующим применением одного и того же преобразования типа к каждому ключу.


Базовые сопоставленные типы

Базовые сопоставленные типы создают новые типы путём перебора всех ключей исходного типа.

Это основа для реализации служебных типов, таких как Partial и Readonly.

Пример

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

// Реализовать служебный тип Partial
// Перебрать все ключи T, добавить модификатор необязательности ?
type Partial<T> = {
    // P перебирает все ключи, возвращаемые keyof T
    // T[P] получает тип значения соответствующего ключа в исходном типе
    [P in keyof T]?: T[P];
};

// Использовать тип Partial
type PartialUser = Partial<User>;

// Тип PartialUser эквивалентен:
// { id?: number; name?: string; email?: string }

// Можно указать только часть свойств
var user: PartialUser = { name: "Alice" };

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

Вывод:

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

Объяснение синтаксиса:[P in keyof T]Указывает на перебор всех ключей типа T.?Модификатор делает свойства необязательными.


Модификаторы свойств

Сопоставленные типы поддерживают несколько модификаторов свойств для изменения характеристик свойств.

Эти модификаторы можно комбинировать для удовлетворения различных потребностей преобразования типов.

Пример

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

// Использовать readonly-отображение: сделать все свойства readonly
// Добавить модификатор readonly
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

// Использовать необязательное сопоставление: сделать все свойства необязательными (базовая версия)
type Optional<T> = {
    [P in keyof T]?: T[P];
};

// Использовать сопоставление с -?: удалить модификатор необязательности (сделать обязательным)
// -? удаляет исходный модификатор ?
type Required<T> = {
    [P in keyof T]-?: T[P];
};

// Проверка типа readonly
var readonlyUser: Readonly<User> = { name: "Alice", age: 25 };
// readonlyUser.age = 30; // Ошибка: свойство readonly нельзя изменить

// Проверка необязательного типа
var optionalUser: Optional<User> = { name: "Bob" };

console.log("Readonly: " + JSON.stringify(readonlyUser));
console.log("Optional: " + JSON.stringify(optionalUser));

Вывод:

只读: {"name":"Alice","age":25}
可选: {"name":"Bob"}

Описание модификатора:?добавляет необязательность,-?удаляет необязательность;readonlyдобавляет readonly,-readonlyудаляет readonly.


Переопределение ключей

Сопоставленные типы также могут использоватьasключевое слово для переопределения имён ключей.

Это полезно, когда нужно единообразно изменять форматы имён ключей.

Пример

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

// Используйте ключевое слово as для переопределения имён ключей
// Добавить префикс ко всем ключам
type WithPrefix<T, Prefix extends string> = {
    // Используйте типы шаблонных литералов для переименования ключей
    // Capitalize делает первую букву заглавной
    [P in keyof T as `${Prefix}${Capitalize<string & P>}`]: T[P];
};

// Используйте WithPrefix, чтобы добавить префикс "user"
type PrefixedUser = WithPrefix<User, "user">;

// Преобразованный тип:
// { userId: number; userName: string; userAge: number }

// Используйте тип с префиксом
var user: PrefixedUser = { userId: 1, userName: "Alice", userAge: 25 };

console.log("С префиксом: " + JSON.stringify(user));

Вывод:

带前缀: {"userId":1,"userName":"Alice","userAge":25}

Типы шаблонных литералов:Использовать`${Prefix}${Capitalize}`может динамически генерировать новые имена ключей.


Фильтрация ключей

Комбинируя условные типы и отображаемые типы, можно добиться фильтрации ключей.

Это очень полезно при реализации служебных типов, таких как Omit.

Пример

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

// Реализовать Omit: исключить указанные ключи
// Использовать условные типы для фильтрации ключей
type Omit<T, K extends keyof T> = {
    // P перебирает все ключи T
    // Если P может быть присвоен K (т.е. находится в списке исключений), вернуть never (исключено)
    // Иначе вернуть P (оставить ключ)
    [P in keyof T as P extends K ? never : P]: T[P];
};

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

// Преобразованный тип:
// { id: number; name: string; email: string }

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

console.log("Без пароля: " + JSON.stringify(user));

Вывод:

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

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


Условное сопоставление

Сопоставленные типы можно комбинировать с условными типами, чтобы применять различные преобразования в зависимости от типов свойств.

Это делает преобразование типов более гибким и интеллектуальным.

Пример

// Определить интерфейс ответа API
interface APIResponse {
    // Данные ответа
    data: string;
    // Сообщение об ошибке
    error: string;
    // Признак загрузки
    isLoading: boolean;
    // Метка времени
    timestamp: number;
}

// Преобразовать типы функций в () => void
// Перебрать все свойства и выполнить условное преобразование в зависимости от типа свойства
type FunctionToVoid<T> = {
    // Если T[P] является типом функции, преобразовать его в () => void
    // В противном случае оставить исходный тип без изменений
    [P in keyof T]: T[P] extends (...args: any[]) => any
        ? () => void
        : T[P];
};

// Использовать условное отображение
var response: FunctionToVoid<APIResponse> = {
    data: "hello",
    error: "",
    isLoading: false,
    timestamp: Date.now()
};

console.log("Response: " + JSON.stringify(response));

Вывод:

响应: {"data":"hello","error":"","isLoading":false,"timestamp":...}

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


Встроенные отображаемые типы

TypeScript имеет много встроенных служебных типов, реализованных на основе отображаемых типов.

Эти служебные типы могут удовлетворить большинство повседневных потребностей разработки.

Пример

// Partial — делает все свойства необязательными
type P1 = Partial<{ a: string; b: number }>;
// Результат: { a?: string; b?: number }

// Required — делает все необязательные свойства обязательными
type R1 = Required<{ a?: string; b?: number }>;
// Результат: { a: string; b: number }

// Readonly — делает все свойства только для чтения
type RO1 = Readonly<{ a: string; b: number }>;
// Результат: { readonly a: string; readonly b: number }

// Pick — выбирает указанные свойства
type PK = Pick<{ a: string; b: number; c: логическое значение }, "a" | "b">;
// Результат: { a: string; b: number }

// Omit — исключает указанные свойства
type OM = Omit<{ a: string; b: number; c: логическое значение }, "c">;
// Результат: { a: string; b: number }

// Тест Partial
console.log("Partial: " + JSON.stringify({} as P1));

// Тест Pick
console.log("Pick: " + JSON.stringify({ a: "x" } as PK));

Комбинации служебных типов:Эти встроенные служебные типы реализованы на основе отображаемых и условных типов. Понимание того, как они работают, поможет вам лучше их использовать.


Примечания

  • Ключевое слово keyof:Используется для получения типа объединения, составленного из всех ключей типа.
  • Ключевое слово in:Используется для перебора типов объединения имён ключей.
  • Позиция модификатора:?иreadonlyПеред именем свойства указывает на добавление модификатора.
  • Модификатор минус:-?и-readonlyИспользуется для удаления модификаторов.
  • ключевое слово as:Используется для переименования имён ключей; должен возвращать строковый или числовой литеральный тип.

Продвинутый уровень:Отображаемые типы можно комбинировать с условными типами и типами шаблонных литералов для реализации сложных преобразований типов.


Итог

Отображаемые типы — одна из самых мощных возможностей системы типов TypeScript.

  • keyof:Получить все ключи типа
  • in:Перебирать имена ключей для отображения
  • ?:Добавить модификаторы необязательности
  • readonly:Добавить модификаторы readonly
  • -?:Удалить модификаторы необязательности
  • as:Переопределить имена ключей

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


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