【问题标题】:Flow error with deep subtypes深度子类型的流错误
【发布时间】:2018-10-13 05:31:11
【问题描述】:

根据流程的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?

有什么想法吗?

【问题讨论】:

    标签: javascript ecmascript-6 flowtype


    【解决方案1】:

    在较高级别上,您可以重用 objectB 实例,只需对 ObjectA 类型稍作修改,也可以创建一个新对象。让我们深入了解您的选择:

    将对象属性标记为协变

    您要做的是将ObjectB 转换为ObjectA。 Flow 抱怨的原因是ObjectAfoo 的类型默认为invariantObjectBfoo 属性是 ObjectAfoo 属性的子类型。为了让 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
    

    将属性标记为“协变”基本上表示您承诺只读取该属性,您不会写入它。看,如果您删除了 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&lt;T&gt;

    【讨论】:

    • 如果我的回复(或其他回复)回答了您的问题,请随时将其标记为已接受的答案(并将您的问题从“未回答”的问题中剔除)
    【解决方案2】:

    据我所知,一种可能的方法是将对象展开。没有这个流是无法读取属性的。

    // @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}; // spread operator applied
    

    【讨论】:

    • 我很确定这只适用于深一层(从我玩这个问题开始)
    猜你喜欢
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-01
    • 1970-01-01
    • 2017-11-10
    • 2020-03-30
    • 1970-01-01
    相关资源
    最近更新 更多