【问题标题】:Argument of type '(number | null)[]' is not assignable to parameter of type 'number[]''(number | null)[]' 类型的参数不能分配给 'number[]' 类型的参数
【发布时间】:2022-01-21 23:10:05
【问题描述】:

我是 ts 新手,刚刚开始将我的应用从 js 迁移到 ts。 第一个函数 - calcAvg,接受一个数字数组并返回它们的平均值。 第二个函数 - 获取对象列表。每个对象都有每日统计数据(参见 DailyData 接口)。 TS 将 calcAvg(marginArray) 标记为错误 - “'(number | null)[]' 类型的参数不可分配给 'number[]' 类型的参数。 输入'号码 | null' 不可分配给类型 'number'。 类型“null”不可分配给类型“number”.ts(2345) const marginRevArray: (number | null)[]"

在 calcAvg 之前,我正在检查数组中的空值,如果有一个或多个空值,我不计算平均值。

任何想法我在这里错过了什么? 谢谢!

***见下面的代码

interface DailyData {
  id: number
  cost: number
  margin: number

}
const calcAvg = (dataList: number[]) => {
  const reducer = (accumulator: number, curr: number) => accumulator + curr;
  const dataSum = dataList.reduce(reducer, 0);
  return dataSum / dataList.length;
};


const avgMargin = (dailyData: DailyData[]) => {
  const marginArray = dailyData.map((obj: DailyData) => obj.cost > 0 ? obj.margin/obj.cost: null);
  if (marginArray.some((el: number|null) => el=== null)) {
    //if the array has null values don't calculate
    return 'no data';
  }
  return calcAvg(marginArray);
};

【问题讨论】:

  • 请提供minimal reproducible example,清楚地表明您面临的问题。理想情况下,有人可以将代码粘贴到像 The TypeScript Playground (link here!) 这样的独立 IDE 中,然后立即着手解决问题,而无需首先重新创建它。所以不应该有伪代码、拼写错误、不相关的错误或未声明的类型或值。
  • 你必须使用类型推断,打字稿不够聪明,无法理解 marginArray 不包含任何空值。所以最后一行应该是return calcAvg(marginArray as number[]);
  • 检查是否有一些值先<= 0,如果有则返回,否则-map。
  • 如果你可以重构使用every而不是some,那么你可以注解使用它作为类型保护like this。你要我写一个答案吗?让我知道。无论哪种方式,您都应该修正问题代码中的拼写错误。
  • @Bergi 感谢您的更正,您说得对,我没有使用正确的术语(我脑残了)。但正确的术语实际上是“断言”。强制转换意味着数据在运行时发生了变化(或者至少是一些关于运行时应该如何解释数据的信息)。这在打字稿中不会发生,因为 javascript 没有改变。

标签: javascript typescript


【解决方案1】:

Array's some() methodTypeScript standard library call signature 看起来像

interface Array<T> {
  some(
    predicate: (value: T, index: number, array: T[]) => unknown, 
    thisArg?: any
  ): boolean;
}

这意味着当您调用marginArray.some(el =&gt; el === null) 时,编译器只知道这将返回一个boolean。它不知道这对marginArray 的类型有任何影响。所以如果调用返回truemarginArray 的类型仍然是(number | null)[],所以编译器会犹豫

calcAvg(marginArray) // error! marginArray might have nulls in it, I think

如果你想摆脱错误不重构,你可以使用type assertion。类型断言适用于像这样的情况,您作为开发人员,对值的类型了解得比编译器所能理解的要多。如果您确定在您调用calcAvg()marginArray 将是number[],那么您可以告诉编译器:

calcAvg(marginArray as number[]); // okay

这行得通,但不是万灵药;通过使用类型断言,您将对其准确性负责。编译器不知道marginArray 中是否有nulls;当你告诉它时,它只会相信你。如果你不小心对编译器撒谎,它不会抓住它:

if (marginArray.some(el => el !== null)) { // ? wrong check
  return 'no data';
}
return calcAvg(marginArray as number[]); // still no error

因此,每当您做出类型断言时,您都应该进行双重和三重检查。


另一方面,如果你不介意一些轻度重构,你可以从some()切换到Array's every() methodstandard library typings includes the following call signature

interface Array<T> {
  every<S extends T>(
    predicate: (value: T, index: number, array: T[]) => value is S, 
    thisArg?: any
  ): this is S[];
}

这表示如果您将user-defined type guard function 作为predicate 传递给narrow 数组元素的类型,那么every() 本身可以用作用户定义的类型保护函数,可以缩小范围整个数组的类型。因此,如果 predicate 被注释,true 返回值意味着 valuenumber 而不是 null,那么来自 every()true 返回值将告诉编译器整个数组是一个number[]

if (!marginArray.every((el: number | null): el is number => el !== null)) {
  return 'no data';
}
return calcAvg(marginArray); // okay

看看如何将回调函数注释为(el: number | null) =&gt; el is number 类型?现在编译器明白,如果达到calcAvg(),那么marginArray 就是number[]

这可能是最接近类型安全的方法。我说“最接近”是因为将函数注释为用户定义的类型保护的行为仍然是开发人员告诉编译器一些它无法弄清楚的事情。如果el =&gt; el !== null 会自动推断 作为类型保护,那就太好了,但事实并非如此。至少有一个关于此的开放功能请求:请参阅microsoft/TypeScript#38390。所以你仍然可以犯上面同样的错误,编译器不会捕捉到它:

if (!marginArray.every((el: number | null): el is number => el === null)) { // ?
  return 'no data';
}

不过,至少在重构版本中,潜在错误的范围更小了。你必须在你写el !== null的地方写el is number,而原始版本让你写marinArray as number[],离相关检查的地方更远。


Playground link to code

【讨论】:

    猜你喜欢
    • 2020-04-04
    • 2018-08-15
    • 2020-06-19
    • 2019-10-23
    • 1970-01-01
    • 2020-06-19
    • 1970-01-01
    • 2017-04-23
    • 2020-05-30
    相关资源
    最近更新 更多