Spec-Zone.ru › Flow

Mapped Types

Mapped types transform object types by iterating over their keys and producing new property types.

type O = {foo: number, bar: string};
type ReadOnly = {readonly [key in keyof O]: O[key]};
TypeScript comparison

Mapped types match TypeScript including distributivity, keyof-based source iteration, and the homomorphic {[K in keyof T]: ...} form.

When to use this​

Use mapped types when you need to systematically transform every property of an existing object type — for example, making all properties optional, read-only, or wrapping their values. If you only need to change a few specific properties, object type spread is simpler.

Basic Usage​

Mapped types have syntax similar to indexed object types but use the in keyword:

type O = {foo: number, bar: string};


type Methodify<T> = () => T;


type MappedType = {[key in keyof O]: Methodify<O[key]>};

In this example, MappedType has all of the keys from O with all of the value types transformed by Methoditfy<O[key]>. The key variable is substituted for each key in O when creating the property, so this type evaluates to:

{
  foo: Methodify<O['foo']>,
  bar: Methodify<O['bar']>,
}
= {
  foo: () => number,
  bar: () => string,
}

Mapped Type Sources​

We call the type that comes after the in keyword the source of the mapped type. The source of a mapped type must be a subtype of string | number | symbol:

type MappedType = {[key in boolean]: number}; // ERROR!

Typically, you'll want to create a mapped type based on another object type. In this case, you should write your mapped type using an inline keyof:

type GetterOf<T> = () => T;
type Obj = {foo: number, bar: string};
type MappedObj = {[key in keyof Obj]: GetterOf<Obj[key]>};

But you do not need to use an object to generate a mapped type. You can also use a union of string literal types to represent the keys of an object type:

type Union = 'foo' | 'bar' | 'baz';
type MappedType = {[key in Union]: number};
// = {foo: number, bar: number, baz: number};

Importantly, when using string literals the union is collapsed into a single object type:

type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};
type MappedFooAndBar = MappedTypeFromKeys<'foo' | 'bar'>;
// = {foo: number, bar: number}, not {foo: number} | {bar: number}

If you use a type like number or string in the source of your mapped type then Flow will create an indexer:

type MappedTypeFromKeys<Keys extends string> = {[key in Keys]: number};
type MappedFooAndBarWithIndexer = MappedTypeFromKeys<'foo' | 'bar' | string>;
// = {foo: number, bar: number, [string]: number}

Distributive Mapped Types​

When the mapped type uses an inline keyof or a type parameter bound by keyof Flow will distribute the mapped type over unions of object types.

For example:

// 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};

Distributive mapped types will also distribute over null and 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

Property Modifiers​

Adding variance and optionality​

You can add readonly or writeonly variance modifiers and the optionality modifier ? in mapped types:

type O = {foo: number, bar: string}
type ReadOnlyPartialO = {readonly [key in keyof O]?: O[key]}; // = {readonly foo?: number, readonly bar?: string};

Removing optionality with -?​

The ? modifier makes every property optional. Prefixing it with - does the opposite: -? removes optionality, turning every optional property into a required one. This works like the Required<T> utility type.

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

-? only clears the optionality of properties that were optional in the source. It does not strip void or undefined from a value type that was already required:

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

-? also works on tuple inputs, where it makes elements no longer optional:

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

Note: Flow does not yet support +readonly/-readonly variance modifiers. They are coming soon.

Mapped Type on Arrays​

Mapped type also works on array or tuple inputs. If the mapped type is in the form of

{[K in keyof <type_1>]: <type_2>}

then type_1 is allowed to be an array or tuple type.

This feature will be especially useful if you want to map over elements of a tuple:

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

For now, the only supported property modifier on array input is the optionality modifier ?.

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

Adoption​

To use mapped types, you need to upgrade your infrastructure so that it supports the syntax:

  • prettier: 3
  • babel with flow-parser/babel-plugin. See our Babel guide for setup instructions.
  • eslint with flow-eslint. See our ESLint guide for setup instructions.

See Also​

  • Indexed Access Types — extracting property types, commonly used in mapped type bodies
  • Conditional Types — another advanced type feature for type-level logic
  • Generics — mapped types rely on generic type parameters
  • Utility Types — built-in types like Partial, Readonly, and Pick (which mapped types generalize)
  • Modernizing Legacy Flow Syntax — migrating the legacy $ObjMap / $TupleMap utilities that mapped types replace

© 2013–present Facebook Inc.
Licensed under the MIT License.
https://flow.org/en/docs/types/mapped-types

Spec-Zone.ru

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