Flow不允许我将`Array<A>`传递给`Array<A | B>`(子类型数组传递给超类型数组)

4
我有一个类型为Array<A>的值(一个子类型数组)。Flow不允许我将其传递到期望Array<A | B>(超类型数组)的位置,尽管它显然可以工作。
例如,我不能将类型为Array<'left' | 'right'>的值分配给类型为Array<string>的变量:
const directions: Array<'left' | 'right'> = ['right', 'left'];
const messages: Array<string> = directions; // error

出现了以下错误:

2: const messages: Array<string> = directions; // error
                                   ^ Cannot assign `directions` to `messages` because in array element: Either string [1] is incompatible with string literal `left` [2]. Or string [1] is incompatible with string literal `right` [3].
    References:
    2: const messages: Array<string> = directions; // error
                             ^ [1]
    1: const directions: Array<'left' | 'right'> = ['right', 'left'];
                               ^ [2]
    1: const directions: Array<'left' | 'right'> = ['right', 'left'];
                                        ^ [3]

尝试 Flow 演示

同样地,即使 NodeANode | BNode,我也不能将 Array<ANode> 传递给接受 Array<Node> 的函数:

type ANode = {type: 'a', value: string};
type BNode = {type: 'b', count: number};

type Node = ANode | BNode;

function getFirstNodeType(nodes: Array<Node>): string {
    return nodes[0].type;
}

// works
const nodesSupertype: Array<Node> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSupertype);

// error
const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSubtype); // error

16: getFirstNodeType(nodesSubtype); // error
                    ^ Cannot call `getFirstNodeType` with `nodesSubtype` bound to `nodes` because property `value` is missing in `BNode` [1] but exists in `ANode` [2] in array element.
References:
6: function getFirstNodeType(nodes: Array<Node>): string {
                                            ^ [1]
15: const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
                                ^ [2]

16: getFirstNodeType(nodesSubtype); // error
                    ^ Cannot call `getFirstNodeType` with `nodesSubtype` bound to `nodes` because string literal `a` [1] is incompatible with string literal `b` [2] in property `type` of array element.
References:
1: type ANode = {type: 'a', value: string};
                        ^ [1]
2: type BNode = {type: 'b', count: number};
                        ^ [2]

试用 Flow 演示


这个问题与Flow数组类型在该类型的子集上失败的问题相同。我发布了这个问题,而不是在那个问题上添加新答案,原因有两个:为了提供更好、更简化的示例让人们理解问题,并提到不同的关键词让搜索此问题的人可以找到。这两个原因中的第一个可能表明这个问题不应该被关闭为重复。 - Rory O'Kane
1个回答

5

Flow 抛出错误是因为它认为你 可能会改变数组

// given the definitions of `Node`, `ANode`, and `BNode` from the question’s second example

function getFirstNodeType(nodes: Array<Node>): string {
    const bNode: BNode = {type: 'b', count: 0}
    // Mutate the parameter `nodes`. Adding a `BNode` is valid according its type.
    nodes.push(bNode);

    return nodes[0].type;
}

const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSubtype); // error

尝试 Flow 演示

在运行上述代码后,即使它被声明为ANode数组,nodesSubtype将包含一个BNode,从而违反了其类型。
有两个解决方案可以让Flow相信您不会改变数组。最清晰的一种是使用Flow实用程序类型$ReadOnlyArray替换Array
function getFirstNodeType(nodes: $ReadOnlyArray<Node>): string { // use $ReadOnlyArray
    return nodes[0].type;
}

const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
getFirstNodeType(nodesSubtype); // no error

尝试 Flow 演示

你只需要在函数参数的类型中进行替换(例如nodes),但如果你愿意,可以在适用的所有地方使用$ReadOnlyArray(例如nodesSubtype)。 $ReadOnlyArray是最安全的解决方案,但你可能不想改变所有现有函数来使用它。在这种情况下,你的替代方法是通过any强制转换数组类型。是的,你一开始就可以这样做,但至少现在你知道为什么可以安全地进行这种转换。你甚至可以留下一个注释来解释为什么存在这个转换,这可能有助于你发现不再有效的假设。
function getFirstNodeType(nodes: Array<Node>): string {
    return nodes[0].type;
}

const nodesSubtype: Array<ANode> = [{type: 'a', value: 'foo'}];
// casting `Array<ANode>` to `Array<Node>` is okay because we know that `getFirstNodeType` won’t actually modify the array
getFirstNodeType(((nodesSubtype: any): Array<Node>));

尝试 Flow 演示

如果您不在意记录问题,可以省略注释并仅将其转换为any类型:
getFirstNodeType((nodesSubtype: any));

尝试Flow演示

其他资源


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接