【问题标题】:How to correctly use a type guard when looping through nested arrays?循环嵌套数组时如何正确使用类型保护?
【发布时间】:2021-10-09 21:01:58
【问题描述】:

在遍历数组时,我需要区分其元素的两种可能类型:

  • 每个元素都是一个字符串,或者
  • 每个元素都是一个字符串数组。
function _addDataAtIndex(
    totalData: Array<string | string[]>,
    dataToAdd: Array<Array<string | string[]>>,
    addIndex: number,
) {
    dataToAdd.forEach((dataArr) => {
        if (dataArr.every((x) => typeof x === 'string')) {
            // each value is a string - insert the entire array
            totalData.splice(addIndex, 0, dataArr);
        } else {
            totalData.splice(addIndex, 0, dataArr[0]);
        }
    });
}

然而,TypeScript 似乎无法推断嵌套数组的类型,即使有看似健壮的类型保护。我仍然需要类型保护来确保我在正确的分支上,但是我必须转换数组 dataArr as string[] 来告诉 TypeScript 没有问题。这是多余且脆弱的,我觉得必须有一种方法可以使用更清洁的类型保护来做到这一点。

我搜索了许多其他问题,但我在那里找到的答案(例如自定义类型保护函数)也不起作用,请参阅this playground

有没有一种干净的方法来区分string | string[]而不改变整个结构?我所能想到的就是将内部值转换为一些自定义联合接口的对象,这对于这个用例来说太混乱了。

【问题讨论】:

    标签: arrays string typescript nested typeguards


    【解决方案1】:

    TypeScript 无法将您的 .every 调用理解为类型保护。您可以为此编写自己的type guard/predicate,尽管这不是必需的。 .every 的类型有两个重载,一个是实际谓词:

        /**
         * Determines whether all the members of an array satisfy the specified test.
         * @param predicate A function that accepts up to three arguments. The every method calls
         * the predicate function for each element in the array until the predicate returns a value
         * which is coercible to the Boolean value false, or until the end of the array.
         * @param thisArg An object to which the this keyword can refer in the predicate function.
         * If thisArg is omitted, undefined is used as the this value.
         */
        every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[];
    
        /**
         * Determines whether all the members of an array satisfy the specified test.
         * @param predicate A function that accepts up to three arguments. The every method calls
         * the predicate function for each element in the array until the predicate returns a value
         * which is coercible to the Boolean value false, or until the end of the array.
         * @param thisArg An object to which the this keyword can refer in the predicate function.
         * If thisArg is omitted, undefined is used as the this value.
         */
        every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
    

    要使用类型保护重载版本,您传递给.every 的谓词本身必须是类型保护。您可以简单地向数组函数添加一个返回类型,将其标记为类型保护:

    function _addDataAtIndex(
        totalData: Array<string | string[]>,
        dataToAdd: Array<Array<string | string[]>>,
        addIndex: number,
    ) {
        dataToAdd.forEach((dataArr) => {
            // Note the `: x is string` here
            if (dataArr.every((x): x is string => typeof x === 'string')) {
                // each value is a string - insert the entire array
                totalData.splice(addIndex, 0, dataArr);
            } else {
                totalData.splice(addIndex, 0, dataArr[0]);
            }
        });
    }
    

    现在它使用 .every 的类型保护版本,这使得 TypeScript 意识到 dataArr 是 if 语句分支中的 string[]

    【讨论】:

    • 这正是我所寻找的——也很好的解释。谢谢!
    猜你喜欢
    • 2019-10-05
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多