Spec-Zone.ru › Flow

Вспомогательные типы

Flow предоставляет набор вспомогательных типов для работы с другими типами и создания новых типов.

Сравнение с TypeScript

Большинство вспомогательных типов Flow напрямую соответствуют типам TypeScript: Readonly, Pick, Omit, Record, Partial, Required, Exclude, Extract, NonNullable, Parameters, ReturnType, ConstructorParameters, InstanceType, Awaited, ThisParameterType, OmitThisParameter, NoInfer, Uppercase, Lowercase, Capitalize, Uncapitalize и keyof. Подробнее см. раздел Концепции TypeScript, которые можно напрямую перенести.

keyof T​

Можно извлечь тип ключей из типа объекта. Обычно это будет объединение типов строковых литералов:

const countries = {
  US: "United States",
  IT: "Italy",
  FR: "France"
};


type Country = keyof typeof countries;


const italy: Country = 'IT'; // Works
const nope: Country = 'nope'; // Error!

В приведённом выше примере тип Country эквивалентен type Country = 'US' | 'IT' | 'FR', но Flow смог извлечь его из ключей countries.

Если вы хотите создать тип перечисления, для вашего случая может лучше подойти перечисление Flow.

Values<T>​

Values<T> представляет собой тип объединения всех типов значений свойств типа объекта или элементов типа массива либо кортежа:

type Props = {
  name: string,
  age: number,
};


// The following two types are equivalent:
type PropValues = string | number;
type PropValues2 = Values<Props>;


const name: PropValues2 = 'Jon';  // Works
const age: PropValues2 = 42;  // Works
const fn: PropValues2 = true; // Error!

Для массивов и кортежей:

type Arr = Array<string>;
type Str = Values<Arr>; //=> string
's' as Str; // OK


type Tup = [1, 2];
type Num = Values<Tup>; //=> 1 | 2
1 as Num; // OK

Обратите внимание: применение Values к typeof литералу объекта или массива приведёт к типу более общему, чем можно было ожидать:

const obj = {
  foo: 1,
  bar: 2,
};


function acceptsValues(x: Values<typeof obj>) { /* ... */ }


acceptsValues(1); // Works
acceptsValues(3); // Works, because the type was interpreted as `number`.

Это поведение изменится, если применить Object.freeze к выражению литерала объекта:

const obj = Object.freeze({
  foo: 1,
  bar: 2,
});


function acceptsValues(x: Values<typeof obj>) { /* ... */ }


acceptsValues(1); // Works
acceptsValues(3); // Error! Because the type was interpreted as `1 | 2`.

Если вы хотите создать тип перечисления, для вашего случая может лучше подойти перечисление Flow.

Сравнение с TypeScript

В TypeScript нет вспомогательного типа Values<T>; его эквивалентом является индексированный доступ T[keyof T].

Readonly<T>​

Readonly<T> — это тип, представляющий версию заданного типа объекта или типа кортежа T только для чтения. Тип объекта только для чтения — это тип объекта, все ключи которого доступны только для чтения. Аналогично, кортеж только для чтения — это кортеж, каждый элемент которого доступен только для чтения.

Это означает, что следующие записи эквивалентны:

type ReadOnlyObj = {
  readonly key: number,  // read-only field, marked by the `readonly` keyword
};
type ReadOnlyTuple = [readonly foo: number];

→

type ReadOnlyObj = Readonly<{
  key: number,
}>;
type ReadOnlyTuple = Readonly<[number]>;

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

type Props = {
  name: string,
  age: number,
};


type ReadOnlyProps = Readonly<Props>;


function render(props: ReadOnlyProps) {
  const {name, age} = props;  // Works
  props.age = 42;             // Error!
}

Кроме того, другие вспомогательные типы, например распространение, могут удалять аннотации чтения и записи, поэтому Readonly<T> — удобный способ снова быстро сделать объект доступным только для чтения после работы с ним:

type Obj = {
  readonly key: number,
};


type MappedObj = Readonly<{...Obj, foo: string}> // Still read-only

Вспомогательный тип Readonly работает с типами объектов и кортежей. Чтобы сделать доступными только для чтения другие типы, можно воспользоваться одним из следующих вариантов:

  • Array<T> -> ReadonlyArray<T>
  • Set<T> -> ReadonlySet<T>
  • Map<K, V> -> ReadonlyMap<K, V>
  • WeakSet<T> -> ReadonlyWeakSet<T>
  • WeakMap<K, V> -> ReadonlyWeakMap<K, V>

Partial<T>​

Этот вспомогательный тип делает все именованные поля объекта или интерфейса необязательными, сохраняя при этом все остальные свойства объекта (например, точность и вариантность).

Он также делает все элементы типа кортежа необязательными.

Пример для объектов:

type Person = {
  name: string,
  age: number,
};
type PartialPerson = Partial<Person>;
// Above equivalent to `{name?: string, age?: number}`


const a: PartialPerson = {}; // OK
const b: PartialPerson = {name: 'George'}; // OK
const c: PartialPerson = {name: 'George', age: 123}; // OK


c as Person; // ERROR: `PartialPerson` is not a `Person`

Для кортежей:

type AllRequired = [number, string];
[] as Partial<AllRequired>; // OK: like `[a?: number, b?: string]` now

Объект или кортеж типа T нельзя передать в Partial<T> из-за изменяемости. Это можно исправить, сделав объект доступным только для чтения:

type Person = {
  name: string,
  age: number,
};


const person: Person = {name: 'George', age: 123};


function noPerson(o: Partial<Person>) {
  // Can mutate:
  o.name = undefined;
}
noPerson(person); // Error!


function okPerson(o: Readonly<Partial<Person>>) {
  // Can't mutate - it's read-only!
}
okPerson(person); // Works

Required<T>​

Вспомогательный тип Required противоположен типу Partial: он делает все необязательные поля объекта или интерфейса обязательными.

Он также делает все элементы типа кортежа обязательными.

Пример для объектов:

type PartialPerson = {
  name?: string,
  age?: number,
};
type Person = Required<PartialPerson>;
// Above equivalent to `{name: string, age: number}`


const a: Person = {name: 'George', age: 123}; // OK
const b: Person = {age: 123}; // ERROR: missing `name` property

Для кортежей:

type AllOptional = [a?: number, b?: string];
[] as Required<AllOptional>; // ERROR: like `[a: number, b: string]` now

Pick<O, Keys>​

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

type O = {foo: number, bar: string, baz: boolean};
type FooAndBar = Pick<O, 'foo' | 'bar'>;


declare const fooAndBar: FooAndBar;
fooAndBar.baz; // error: baz is missing
fooAndBar.foo as number; // ok
fooAndBar.bar as string; // ok

Omit<O, Keys>​

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

type O = {foo: number, bar: string, baz: boolean};
type JustBaz= Omit<O, 'foo' | 'bar'>;


declare const justBaz: JustBaz;
justBaz.baz as boolean; // ok
justBaz.foo; // error: missing foo
justBaz.bar; // error: missing bar

Record<Keys, Type>​

Этот вспомогательный тип позволяет создать тип объекта из объединения ключей, задав для каждого поля Type.

type NumberRecord = Record<'foo' | 'bar', number>;
declare const numberRecord: NumberRecord;
numberRecord.foo as number; // ok
numberRecord.bar as number; // ok
numberRecord.baz; // error

Обратите внимание: Record отличается от использования индексатора:

type NumberRecord = Record<'foo' | 'bar', number>;
type IndexedObject = {['foo' | 'bar']: number};


// Record uses explicit fields, which means they are all required
const rec: NumberRecord = {}; // error
// Indexers do not have this same requirement
const idx: IndexedObject = {}; // no error

Exclude<T, U>​

Этот вспомогательный тип исключает все подтипы U из T.

type T = Exclude<1 | 2 | 3 | 4, 1 | 3>; // evaluates to 2 | 4
1 as T; // error
2 as T; // ok
3 as T; // error
4 as T; // ok

Extract<T, U>​

Этот вспомогательный тип оставляет только подтипы U из T.

declare class Car {}
declare class Animal {}
declare class Dog extends Animal {}
declare class Cat extends Animal {}
type T = Extract<Car | Dog | Cat, Animal>; // evaluates to Dog | Cat
new Car() as T; // error
new Dog() as T; // ok
new Cat() as T; // ok

ReturnType<F>​

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

declare function f(s: string, n: number): boolean;
type Bool = ReturnType<typeof f>;
true as Bool;
1 as Bool; // Error: number is not boolean

Parameters<F>​

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

declare function f(s: string, n: number): boolean;
type Tuple = Parameters<typeof f>; // Evaluates to [string, number]
's' as Tuple[0];
1 as Tuple[1];
false as Tuple[2]; // Error: tuple type only has two elements

ConstructorParameters<C> ≥0.317​

Этот вспомогательный тип извлекает типы параметров конструктора класса и помещает их в тип кортежа. Передайте сам класс через typeof:

class Point {
  x: number;
  y: number;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}


type PointArgs = ConstructorParameters<typeof Point>; // Evaluates to [number, number]
[1, 2] as PointArgs; // OK
'x' as PointArgs[0]; // Error: string is not number

Если у класса нет явного конструктора, используется синтезированный конструктор по умолчанию, который не принимает аргументов:

class Empty {}
type EmptyArgs = ConstructorParameters<typeof Empty>; // Evaluates to []
[] as EmptyArgs; // OK
[1] as EmptyArgs; // Error: default constructor takes no arguments

Класс без собственного конструктора наследует параметры конструктора родительского класса:

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}
class Dog extends Animal {} // no explicit constructor


type DogArgs = ConstructorParameters<typeof Dog>; // Inherited [string]
['Rex'] as DogArgs; // OK
[1] as DogArgs; // Error: number is not string

InstanceType<C> ≥0.317​

Этот вспомогательный тип извлекает тип экземпляра, создаваемого конструктором класса. Передайте сам класс через typeof:

class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}


type UserInstance = InstanceType<typeof User>; // Evaluates to User
declare const u: UserInstance;
u.name as string; // OK
u as User; // OK

Это полезно, если у вас есть тип класса (статическая сторона, например, из ограничения обобщённого типа или Class<T>), а нужен тип создаваемых им значений.

Для обобщённого класса без контекста вывода параметры типа класса принимают значения своих ограничений (по умолчанию — unknown):

class Box<T> {
  value: T;
  constructor(value: T) {
    this.value = value;
  }
}


type BoxInstance = InstanceType<typeof Box>; // Box<unknown>
declare const b: BoxInstance;
b.value as unknown; // OK

ThisParameterType<F>​

Этот вспомогательный тип извлекает тип параметра this из заданного типа функции.

type T = ThisParameterType<(this: number, bar: string) => void>; // Evaluates to number
'1' as T; // error
2 as T; // ok

OmitThisParameter<F>​

Этот вспомогательный тип удаляет параметр this из заданного типа функции.

type HasThisParamFun = (this: number, bar: string) => void;
type NoThisParamFun = OmitThisParameter<HasThisParamFun> // Evaluates to (bar: string) => void
declare const hasThisParam: HasThisParamFun;
declare const noThisParam: NoThisParamFun;


hasThisParam(''); // error: global object is not number
noThisParam(''); // ok: no this type requirement

Awaited<T>​

Awaited<T> снимает обёртку типа Promise, чтобы извлечь тип разрешённого значения. Если T не является Promise, он возвращает T без изменений.

type Response = Awaited<Promise<string>>; // Evaluates to string
'hello' as Response; // OK
42 as Response; // Error: number is not string

Это полезно, когда нужно обратиться к разрешённому типу Promise, не снимая обёртку вручную. Если T не является Promise, Awaited<T> просто вычисляется в T:

type A = Awaited<Promise<number>>; // number
type B = Awaited<string>; // string (non-Promise types are returned as-is)


42 as A; // OK
'hello' as B; // OK
'hello' as A; // Error: string is not number
42 as B; // Error: number is not string

Можно объединить Awaited с другими вспомогательными типами, например ReturnType, чтобы извлечь разрешённый тип из возвращаемого значения асинхронной функции:

async function fetchUser(): Promise<{name: string}> {
  return {name: 'George'};
}


type User = Awaited<ReturnType<typeof fetchUser>>;


const user: User = {name: 'George'}; // OK
const bad: User = 'not a user'; // Error

NonNullable<T>​

NonNullable<T> преобразует тип T в тип, который не является maybe-типом. Иными словами, значения NonNullable<T> — это значения T, за исключением null и undefined.

type MaybeName = ?string;
type Name = NonNullable<MaybeName>;


'Gabriel' as MaybeName; // Works
null as MaybeName; // Works
'Gabriel' as Name; // Works
null as Name; // Error! `null` can't be annotated as Name because Name is not a maybe type

NoInfer<T>​

NoInfer<T> не позволяет выводить параметр типа по обёрнутой позиции. При выводе Flow параметра типа T обычно учитывает все позиции, в которых встречается T. Если обернуть позицию в NoInfer, она не будет учитываться при выводе, поэтому T определяется только по оставшимся (необёрнутым) позициям. Полученный тип по-прежнему является T — NoInfer влияет только на вывод, но не на сам тип.

Это полезно, когда у обобщённой функции несколько параметров используют один и тот же параметр типа, но нужно, чтобы на вывод влияли только некоторые из них. Без NoInfer Flow может расширить параметр типа, чтобы учесть все аргументы, и тогда несовпадения останутся незамеченными.

Рассмотрим функцию, в которой оба параметра используют один и тот же параметр типа T:

declare function assertEqual<T>(actual: T, expected: T): void;


assertEqual('hello', 42); // No error: T is inferred as string | number

Поскольку оба аргумента участвуют в выводе, Flow расширяет T до string | number, и вызов выполняется успешно, хотя типы двух аргументов различаются.

Обёрнув параметр expected в NoInfer, можно добиться того, чтобы T выводился только по actual:

declare function assertEqual<T>(actual: T, expected: NoInfer<T>): void;


assertEqual('hello', 'world'); // OK
assertEqual('hello', 42); // Error!

Теперь T выводится как string только по первому аргументу, и 42 не соответствует ему.

Вложенные типы

NoInfer также работает, если T встречается внутри более сложного типа. Все вхождения параметра типа внутри NoInfer исключаются из вывода:

declare function createSignal<T>(
  initialValue: T,
  defaultValue: NoInfer<T>,
): void;


createSignal('hello', 'default'); // OK
createSignal('hello', 42); // Error!

Функции обратного вызова

Можно использовать NoInfer в позициях параметров функции обратного вызова, чтобы она не влияла на параметр типа, но при этом получала выведенный тип:

declare function filter<T>(
  items: Array<T>,
  predicate: (item: NoInfer<T>) => boolean,
): Array<T>;


const numbers: Array<number> = [1, 2, 3];
filter(numbers, (item) => item > 1); // OK

Здесь T выводится как number по аргументу items. Функция обратного вызова predicate получает item типа number, но не участвует в выводе T.

$KeyMirror<O>​

$KeyMirror<Obj> сопоставляет каждому свойству объекта тип ключа этого свойства. Например:

const obj = {
  a: true,
  b: 'foo'
};


declare function run<O extends {...}>(o: O): $KeyMirror<O>;


// newObj is of type {a: 'a', b: 'b'}
const newObj = run(obj);


newObj.a as 'a'; // Works
newObj.b as 'a'; // Error! String 'b' is incompatible with 'a'

Class<T>​

Если тип T представляет экземпляры класса C, то тип Class<T> является типом самого класса C. Например:

class Store {}
class ExtendedStore extends Store {}
class Model {}


function makeStore(storeClass: Class<Store>) {
  return new storeClass();
}


makeStore(Store) as Store;
makeStore(ExtendedStore) as Store;
makeStore(Model) as Model; // Error!

Для классов с параметрами типа необходимо также указать этот параметр. Например:

class ParamStore<T> {
  constructor(data: T) {}
}


function makeParamStore<T>(storeClass: Class<ParamStore<T>>, data: T): ParamStore<T> {
  return new storeClass(data);
}
makeParamStore(ParamStore, 1) as ParamStore<number>;
makeParamStore(ParamStore, 1) as ParamStore<boolean>; // Error!
Сравнение с TypeScript

В TypeScript нет встроенного аналога Class<T>; обычно используют записи new (...args: any[]) => T или typeof C для конкретного класса.

$Exports<T>​

Следующие варианты функционально эквивалентны

import typeof * as T from 'my-module';
type T = $Exports<'my-module'>;

Преимущество синтаксиса $Exports в том, что тип можно export в той же строке

export type T = $Exports<'my-module'>;

тогда как в противном случае в варианте import typeof пришлось бы экспортировать псевдоним

import typeof * as T from 'my-module';
export type MyModuleType = T;

StringPrefix и StringSuffix​

Типы StringPrefix и StringSuffix представляют строки с заданным префиксом и суффиксом соответственно. Первый аргумент типа должен быть типом строкового литерала, задающим префикс или суффикс.

С помощью StringPrefix можно определить тип, принимающий строки, которые начинаются с data-:

type DataProp = StringPrefix<'data-'>;
'data-foo' as DataProp; // OK
'data-bar' as DataProp; // OK
'random string' as DataProp; // ERROR

С помощью StringSuffix можно определить тип, принимающий строки, которые заканчиваются на !:

type Exclaim = StringSuffix<'!'>;
'yay!' as Exclaim; // OK
'woo!' as Exclaim; // OK
'random string' as Exclaim; // ERROR

Их можно объединять с помощью типов-пересечений:

type CSSVar = StringPrefix<'var(--'> & StringSuffix<')'>;
'var(--color)' as CSSVar; // OK
'random string' as CSSVar; // ERROR

Оба вспомогательных типа принимают необязательный второй аргумент типа, задающий тип оставшейся части:

type Price = StringPrefix<'$', '1' | '2'>;
'$1' as Price; // OK
'$2' as Price; // OK
'$999' as Price; // ERROR

Если он не указан, тип оставшейся части — просто string.

Uppercase<T>, Lowercase<T>, Capitalize<T>, Uncapitalize<T> ≥0.316​

Эти четыре вспомогательных типа меняют регистр строкового типа:

  • Uppercase<T> переводит все символы в верхний регистр.
  • Lowercase<T> переводит все символы в нижний регистр.
  • Capitalize<T> переводит первый символ в верхний регистр, оставляя остальные без изменений.
  • Uncapitalize<T> переводит первый символ в нижний регистр, оставляя остальные без изменений.

Аргумент должен быть типом строки. Если применить его к типу строкового литерала, результатом будет преобразованный литерал:

type Greeting = Uppercase<'hello'>; // 'HELLO'
'HELLO' as Greeting; // OK
'hello' as Greeting; // ERROR
type Tag = Lowercase<'WARNING'>; // 'warning'
'warning' as Tag; // OK
'WARNING' as Tag; // ERROR

Capitalize и Uncapitalize изменяют только первый символ:

type Name = Capitalize<'flow'>; // 'Flow'
'Flow' as Name; // OK
'flow' as Name; // ERROR
'FLOW' as Name; // ERROR
type Field = Uncapitalize<'FullName'>; // 'fullName'
'fullName' as Field; // OK
'FullName' as Field; // ERROR
'fullname' as Field; // ERROR (the tail is left untouched)

Они распределяются по типам объединения, преобразуя каждый элемент независимо:

type Direction = Uppercase<'north' | 'south'>; // 'NORTH' | 'SOUTH'
'NORTH' as Direction; // OK
'SOUTH' as Direction; // OK
'south' as Direction; // ERROR

Их можно вкладывать друг в друга, чтобы комбинировать преобразования:

type RoundTrip = Uncapitalize<Capitalize<'foo'>>;
'foo' as RoundTrip; // OK


type Nested = Uppercase<Lowercase<'AbC'>>;
'ABC' as Nested; // OK

При применении к общему типу string результатом также будет string, но он будет представлять только строки, уже имеющие преобразованный вид. Конкретный литерал входит в этот тип, только если он уже имеет такой вид:

type Loud = Uppercase<string>;
declare const x: Loud;
x as string; // OK
'ANYTHING' as Loud; // OK
'anything' as Loud; // ERROR (not already upper case)

$Exact<T> (Не рекомендуется)​

Предупреждение

Избегайте $Exact<T>. Создайте точный тип объекта, а затем получайте из него неточные варианты с помощью распространения типа объекта.

$Exact делает неточный тип объекта точным:

type InexactUser = {name: string, ...};
type ExactUser = $Exact<InexactUser>;


const user = {name: 'John Wilkes Booth'};
// These will both be satisfied because they are equivalent:
const a: ExactUser = user;
const b: {name: string} = user;

Предпочтительнее обратный подход: определить точный тип и распространить его в неточный.

type ExactUser = {name: string};
type InexactUser = {...ExactUser, ...};


const user = {name: 'John Wilkes Booth'};
const a: ExactUser = user;

См. также​

  • Отображаемые типы — общий механизм преобразования типов объектов (обобщает многие вспомогательные типы)
  • Условные типы — условные конструкции на уровне типов для более сложных преобразований
  • Обобщённые типы — большинство вспомогательных типов являются обобщёнными; понимание обобщённых типов помогает эффективно их использовать
  • Типы индексированного доступа — извлечение типов свойств из объектов и кортежей
  • Модернизация устаревшего синтаксиса Flow — переход от вспомогательных типов с префиксом $, таких как $ObjMap, $PropertyType и $NonMaybeType, к их современным эквивалентам

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

Spec-Zone.ru

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