Сопоставленные типы
Сопоставленные типы преобразуют объектные типы, перебирая их ключи и создавая новые типы свойств.
type O = {foo: number, bar: string};
type ReadOnly = {readonly [key in keyof O]: O[key]}; Сопоставленные типы соответствуют TypeScript, включая дистрибутивность, итерацию по исходному типу на основе keyof и гомоморфную форму {[K in keyof T]: ...}.
Когда это использовать
Используйте сопоставленные типы, когда нужно систематически преобразовать каждое свойство существующего объектного типа — например, сделать все свойства необязательными, доступными только для чтения или обернуть их значения. Если нужно изменить лишь несколько определённых свойств, проще использовать объединение объектных типов.
Основное использование
Синтаксис сопоставленных типов похож на синтаксис индексированных объектных типов, но в нём используется ключевое слово in:
type O = {foo: number, bar: string};
type Methodify<T> = () => T;
type MappedType = {[key in keyof O]: Methodify<O[key]>}; В этом примере MappedType содержит все ключи из O, а типы всех значений преобразованы с помощью Methoditfy<O[key]>. Переменная key подставляется для каждого ключа в O при создании свойства, поэтому этот тип принимает следующий вид:
{
foo: Methodify<O['foo']>,
bar: Methodify<O['bar']>,
}
= {
foo: () => number,
bar: () => string,
} Источники сопоставленных типов
Тип, следующий за ключевым словом in, называется источником сопоставленного типа. Источник сопоставленного типа должен быть подтипом string | number | symbol:
type MappedType = {[key in boolean]: number}; // ERROR! Обычно сопоставленный тип создают на основе другого объектного типа. В этом случае следует записать сопоставленный тип, используя встроенный keyof:
type GetterOf<T> = () => T;
type Obj = {foo: number, bar: string};
type MappedObj = {[key in keyof Obj]: GetterOf<Obj[key]>}; Однако для создания сопоставленного типа не обязательно использовать объект. В качестве ключей объектного типа также можно использовать объединение строковых литеральных типов:
type Union = 'foo' | 'bar' | 'baz';
type MappedType = {[key in Union]: number};
// = {foo: number, bar: number, baz: number}; Важно, что при использовании строковых литералов объединение сворачивается в единый объектный тип:
type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};
type MappedFooAndBar = MappedTypeFromKeys<'foo' | 'bar'>;
// = {foo: number, bar: number}, not {foo: number} | {bar: number} Если в качестве источника сопоставленного типа использовать тип вроде number или string, Flow создаст индексатор:
type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};
type MappedFooAndBarWithIndexer = MappedTypeFromKeys<'foo' | 'bar' | string>;
// = {foo: number, bar: number, [string]: number} Дистрибутивные сопоставленные типы
Если в сопоставленном типе используется встроенный keyof или параметр типа, ограниченный с помощью keyof, Flow распределит сопоставленный тип по объединениям объектных типов.
Например:
// This mapped type uses keyof inline
type MakeAllValuesNumber<O extends {...}> = {[key in keyof O]: number};
type ObjWithFoo = {foo: string};
type ObjWithBar = {bar: string};
type DistributedMappedType = MakeAllValuesNumber<
| ObjWithFoo
| ObjWithBar
>; // = {foo: number} | {bar: number};
// This mapped type uses a type parameter bound by keyof
type Pick<O extends {...}, Keys extends keyof O> = {[key in Keys]: O[key]};
type O1 = {foo: number, bar: number};
type O2 = {bar: string, baz: number};
type PickBar = Pick<O1 | O2, 'bar'>; // = {bar: number} | {bar: string}; Дистрибутивные сопоставленные типы также распределяются по null и undefined:
type Distributive<O extends ?{...}> = {[key in keyof O]: O[key]};
type Obj = {foo: number};
type MaybeMapped = Distributive<?Obj>;// = ?{foo: number}
null as MaybeMapped; // OK
undefined as MaybeMapped; // OK
({foo: 3}) as MaybeMapped; // OK Модификаторы свойств
Добавление вариантности и необязательности
В сопоставленных типах можно добавлять модификаторы вариантности readonly или writeonly, а также модификатор необязательности ?:
type O = {foo: number, bar: string}
type ReadOnlyPartialO = {readonly [key in keyof O]?: O[key]}; // = {readonly foo?: number, readonly bar?: string}; Удаление необязательности с помощью -?
Модификатор ? делает каждое свойство необязательным. Добавление перед ним - даёт противоположный эффект: -? удаляет необязательность, превращая каждое необязательное свойство в обязательное. Это работает так же, как утилитарный тип Required<T>.
type O = {foo: number, bar?: string};
type RequiredO = {[key in keyof O]-?: O[key]}; // = {foo: number, bar: string}
({foo: 1}) as RequiredO; // ERROR: bar is now required -? удаляет признак необязательности только у свойств, которые были необязательными в исходном типе. Он не удаляет void или undefined из типа значения, которое уже было обязательным:
type T = {a: string | void};
type Mapped = {[K in keyof T]-?: T[K]};
declare const m: Mapped;
m.a as string | void; // OK: `a` was not optional, so its value type is unchanged
m.a as string; // ERROR: void is still part of the value type -? также работает с кортежами, делая их элементы обязательными:
type Tuple = [a: string, b?: number, c?: boolean];
type AllRequired = {[K in keyof Tuple]-?: Tuple[K]};
(['']) as AllRequired; // ERROR: elements 1 and 2 are now required
declare const t: AllRequired;
t as [string, number, boolean]; // OK Примечание: Flow пока не поддерживает модификаторы вариантности
+readonly/-readonly. Эта возможность появится в ближайшее время.
Сопоставленный тип для массивов
Сопоставленный тип также работает с массивами и кортежами. Если сопоставленный тип имеет вид
{[K in keyof <type_1>]: <type_2>} то в качестве type_1 можно использовать тип массива или кортежа.
Эта возможность особенно полезна, если нужно сопоставить типы элементов кортежа:
type Tuple = [readonly a: number, b?: string];
type MappedTuple = {[K in keyof Tuple]: Tuple[K] extends number ? boolean : string};
const a: MappedTuple[0] = true;
const b: MappedTuple[1] = '';
'' as MappedTuple[0] // error
false as MappedTuple[1] // error
declare const mapped: MappedTuple;
mapped[0] = true; // error: cannot-write Пока для массива в качестве входных данных поддерживается только модификатор необязательности ?.
type Tuple = [readonly a: number, b?: string];
type Supported = {[K in keyof Tuple]?: string};
type Unsupported1 = {readonly [K in keyof Tuple]: string}; // Error
type Unsupported2 = {writeonly [K in keyof Tuple]: string}; // Error Подключение
Чтобы использовать сопоставленные типы, необходимо обновить инфраструктуру, чтобы она поддерживала этот синтаксис:
-
prettier: 3 -
babelсflow-parser/babel-plugin. Инструкции по настройке см. в руководстве по Babel. -
eslintсflow-eslint. Инструкции по настройке см. в руководстве по ESLint.
См. также
- Типы индексированного доступа — получение типов свойств, часто используемое в телах сопоставленных типов
- Условные типы — ещё одна продвинутая возможность типов для логики на уровне типов
- Обобщённые типы — сопоставленные типы используют обобщённые параметры типа
-
Утилитарные типы — встроенные типы, такие как
Partial,ReadonlyиPick(которые обобщают сопоставленные типы) -
Обновление устаревшего синтаксиса Flow — переход с устаревших утилитарных типов
$ObjMap/$TupleMap, которые заменяют сопоставленные типы
© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/mapped-types