На высоком уровне вы можете либо повторно использовать экземпляр objectB
с небольшим изменением типа ObjectA, либо создать новый объект. Давайте покопаемся в ваших вариантах:
Пометка свойства объекта как ковариантного
То, что вы пытаетесь сделать, это разыграть ObjectB
до ObjectA
. Причина, по которой Flow жалуется, состоит в том, что тип foo
в ObjectA
по умолчанию инвариант . Свойство foo
ObjectB
является подтипом свойства foo
ObjectA
. Чтобы Flow понял это, нам просто нужно пометить свойство foo
как ковариант :
( Попробуйте )
// @flow
type ObjectA = { +foo: { bar: string } };
type ObjectB = { foo: { bar: string, baz: string } };
let objectB: ObjectB = { foo: { bar: '123', baz: '456' } };
let objectA: ObjectA = objectB; // Woohoo, no error
Пометка свойства как «ковариантного» в основном говорит о том, что вы обещаете читать только это свойство, но не будете писать в него. Смотрите, если вы удалите свойство baz
объекта A, оно удалит baz
из объекта B. Отметив его как ковариантный, Flow выдаст ошибку, если вы напишите ему:
( Попробуйте )
// @flow
type ObjectA = { +foo: { bar: string } };
type ObjectB = { foo: { bar: string, baz: string } };
let objectB: ObjectB = { foo: { bar: '123', baz: '456' } };
let objectA: ObjectA = objectB;
objectA.foo = {bar: 'oh-oh, deleted baz in objectB'}; //Error
Этот шаблон также работает для объектов с более глубокими вложениями:
( Попробуйте )
// @flow
type ObjectA = { +foo: { +bar: { baz: string } } };
type ObjectB = { foo: { bar: { bax: string, baz: string } } };
let objectB: ObjectB = { foo: { bar: { bax: '123', baz: '456' } } };
let objectA: ObjectA = objectB; // Woohoo, no error
См. Документацию Flow по подтипу глубины для получения более подробной информации об этом наборе.
Использование $ReadOnly<T>
Flow имеет тип utlity, $ReadOnly<T>
, чтобы пометить все свойства объекта как ковариантные, поэтому вы можете использовать это вместо:
( Попробуйте )
// @flow
type ObjectA = $ReadOnly<{ foo: { bar: string } }>;
type ObjectB = { foo: { bar: string, baz: string } };
let objectB: ObjectB = { foo: { bar: '123', baz: '456' } };
let objectA: ObjectA = objectB; // Woohoo, no error
Или вы можете создать экземпляр ObjectA ReadOnly и оставить определение ObjectA в покое:
( Попробуйте )
// @flow
type ObjectA = { foo: { bar: string } };
type ObjectB = { foo: { bar: string, baz: string } };
let objectB: ObjectB = { foo: { bar: '123', baz: '456' } };
let objectA: $ReadOnly<ObjectA> = objectB; // Woohoo, no error
Создание нового объекта
В качестве альтернативы, вы можете создать новую копию объекта с разворотом и избежать всего этого:
( Попробуйте )
// @flow
type ObjectA = { foo: { bar: string } };
type ObjectB = { foo: { bar: string, baz: string } };
let objectB: ObjectB = { foo: { bar: '123', baz: '456' } };
let objectA: ObjectA = {...objectB} // Create a new object
Но это сработает только на один уровень и создаст дополнительный объект. Обычно я пропускаю эту опцию и в итоге использую $ReadOnly<T>
, когда мне нужно использовать объект только для чтения.