Spec-Zone.ru › Flow

Сопоставленные типы

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

type O = {foo: number, bar: string};
type ReadOnly = {readonly [key in keyof O]: O[key]};
Сравнение с TypeScript

Сопоставленные типы соответствуют 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API