【问题标题】:(Number | undefined)[] is not assignable to Number[](Number | undefined)[] 不可分配给 Number[]
【发布时间】:2020-08-20 01:52:27
【问题描述】:

我在打字稿中遇到了类型问题。

所以我有一个从一些复选框中获得的 som ID 数组。这个数组也可以是空的。

可从 submit() 返回的值示例:

const responseFromSubmit = {
1: {
  id: "1",
  value: "true"
  },
2: {
  id: "2",
  value: "false"
  },
3: {
  id: "3",
  value: "false"
  } 
};



const Ids: number[] = Object.values(submit()!)
  .map(formfield => {
    if (formfield.value === 'true') {
      return Number(formfield.id);
    }
  })
  .filter(id => id != undefined);

所以在这种情况下,ID 将是 Ids = [1]。

我尝试了几种解决方案,例如通过检查 Ids 是否未定义来尝试在上面的代码块之后更改 Ids 的值:

if (ids.length > 0){
  ids = []
}

通过此代码,常量 Ids 的类型为 (Number | undefined)[],我怎样才能使其始终为 number[] 类型,即使它为空?

这是一个解决方案,但我一点也不喜欢:

const Ids: number[] = Object.values(submit()!)
  .map(formfield => {
    if (formfield.value === 'true') {
      return Number(formfield.id);
    } else {
      return 0;
    }
  })
  .filter(id => id != 0);

在我的情况下,formfield.id 永远不会有值 0,因此可以过滤所有值为 0 的元素。所以我不推荐这个解决方案。但是,嘿,它有效,对吧? ¯\_(ツ)_/¯

【问题讨论】:

  • 问题是.filter不能改变数组的类型。这是有道理的,比如说,你有arr = [1, 2, "a", "b", 3],即(number | string)[]。如果您执行arr.filter(x => x !== "a"),那与arr.filter(x => typeof x === "number") 的操作(签名方式)相同。 TS 无法推断出一个过滤器会产生number[] 而另一个不会。它需要检查代码,并且可能能够进行比这更高级的分析。
  • submit() 是什么?
  • submit() 是我制作的一个函数,它从表单返回值。在这种情况下,它会返回复选框中的所有值 :)
  • 0的情况呢?你也想过滤掉吗?

标签: javascript arrays typescript array.prototype.map


【解决方案1】:

问题

主要问题是.filter() 签名。它会总是返回一个与你开始时相同类型的数组。 TypeScript 编译器无法保证其他任何事情。这是一个例子:

const arr/*: (string | number) */ = ["one", 2, 3, "four", 5, 6];

const numbers/*: number[]*/ = arr.filter(x => typeof x === "number");

console.log(numbers);

Playground Link

如果您忽略类型,这有效,但它在功能上等同于以下内容:

const arr/*: (string | number)[]*/ = ["one", 2, 3, "four", 5, 6];

const numbers/*: number[]*/ = arr.filter(x => x !== "one");

console.log(numbers);

Playground Link

在这两种情况下,您都有一个混合类型数组和 一些 过滤功能。为了保证结果只是特定类型,您需要检查代码并进行推断。这不是编译器的工作方式,但是 - 在 Array<T | U> 上调用 .filter() 只能再次生成 Array<T | U>,泛型保持不变。

解决方案

您可以做的是颠倒您的.map.filter 的顺序。您需要重新编写它们,但它会在类型方面正常工作。我还使逻辑更加连贯 - 现在你正在 double 隐式过滤。 map() 只会转换某些类型,而不是其他类型,因此会进行间接过滤。实际的.filter() 调用然后筛选未映射/软过滤的值。

因此,正确的逻辑和正确的类型保留如下:

const Ids: number[] = Object.values(submit()!)
  .filter(formfield => formfield.value === 'true')
  .map(formfield => Number(formfield.id))

Playground Link

这是您想要的更短且更正确的逻辑形式。

  • 真正的过滤条件formfield.value === 'true'.filter()调用中自行提取。
  • .filter() 运行 first 所以你可以保证从编译器的角度来看具有相同的类型并且你只是将列表缩小到只有你感兴趣的项目在。
  • .map() 并不完全符合它的意思 - 数组的每个值的 1:1 映射。它不需要做任何更复杂的逻辑。因此,为了执行转换,它不需要关心什么是正确的或不正确的。

【讨论】:

  • 谢谢!这是一个完美的解决方案。你的解释很清楚:)
【解决方案2】:

我无法直接回答您的问题,但请允许我提出不同的方法:

const ids = Object.keys( obj || {} )
      .reduce( function(acc,cur) {
                   if( obj[cur] ) acc.push(cur);
                   return acc
                },
                []
              )

听起来您想从对象中提取值的子集。这将返回真实的密钥。而不是acc.push(cur),你会想要推送Number(formfield.id)的等价物。顶部的obj || {} 允许'obj' 为undefined

【讨论】:

    【解决方案3】:

    尝试添加:

        if (formfield.value === 'true') {
          return Number(formfield.id);
        }
        return null;
    

    在if中的return下。

    完整代码:

    const Ids: number[] = Object.values(submit()!)
      .map(formfield => {
        if (formfield.value === 'true') {
          return Number(formfield.id);
        }
        return null;
      })
      .filter(id => id != undefined);
    

    编辑:

    检查一个变量是否未定义的更好方法是使用typeof 运算符:

    typeof id !== 'undefined'
    

    【讨论】:

    • 感谢您的回答!这会将类型更改为 (number | null)[]
    • 是的,这仍然不适用于严格的编译器选项。它仅在您接受 null 作为 number 的成员时才有效(在松散的编译器检查中),但我认为这是不可取的。
    • 你能给我们从 submit() 出来的数据数组吗?为了做一些测试。编辑问题以执行此操作。
    • @aepifano 很容易测试here。我已经删除了一些不相关的细节。重要的是它正在处理一系列类型,然后对其进行转换。 Object.values 不需要查看打字。
    • 我编辑了问题以提供响应示例
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-07
    • 2021-09-18
    • 2023-02-22
    • 1970-01-01
    • 2022-10-22
    • 2017-01-05
    相关资源
    最近更新 更多