根据 Subtypes of objects 文档的流程,这是有效的
// @flow
type ObjectA = { foo: string };
type ObjectB = { foo: string, bar: number };
let objectB: ObjectB = { foo: 'test', bar: 42 };
let objectA: ObjectA = objectB; // Works!
但是更深层次的实现并没有
// @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; // Error! Why?
有什么想法吗?
最佳答案
在高层次上,您可以通过对 ObjectA 类型进行轻微修改来重用 objectB
实例,也可以创建一个新对象。让我们深入了解您的选择:
将对象属性标记为协变
您要做的是将 ObjectB
转换为 ObjectA
。 Flow 提示的原因是 foo
中 ObjectA
的类型默认是 invariant。 foo
的 ObjectB
属性是 foo
的 ObjectA
属性的子类型。为了让 Flow 理解这一点,我们只需要将 foo
属性标记为 covariant :
( Try )
// @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
将属性标记为“协变”基本上表示您 promise 只读取该属性,不会写入它。看,如果您删除了 objectA 的 baz
属性,它会从 objectB 中删除 baz
。通过将其标记为协变,Flow 将在您写入时抛出错误:
( Try )
// @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
此模式也适用于嵌套更深的对象:
( Try )
// @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
有关此类型的更多详细信息,请参阅 depth subtyping 上的 Flow 文档。
使用 $ReadOnly<T>
Flow 有一个实用类型 $ReadOnly<T>
,用于将对象的所有属性标记为协变的,因此您可能想改用它:
( Try )
// @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 定义:
( Try )
// @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
创建一个新对象
或者,您可以使用展开创建对象的新副本并避免所有这些输入:
( Try )
// @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>
。
关于javascript - 深度子类型的流错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50145934/