JS 到 TS:类型 'null' 不可分配给类型 'number'

JS to TS: Type 'null' is not assignable to type 'number'

我是 TypeScript 的新手,刚开始将我的应用程序从 JavaScript 迁移到 TypeScript。

  1. 第一个函数:calcAvg,接受一组数字和 returns 它们的平均值。
  2. 第二个函数:avgRoasDiscrepancy,接受两个数组,如果两个数组长度相等且没有空值,它将从一个列表中减去每个元素,其中元素在第二个列表中的索引相同数组。

问题出在以下 map 函数中(请参阅下面的完整代码):

const discArray = clientRoasArray.map((value: number, index: number) => value - roasArray[index]);

我收到以下错误:

Argument of type '(value: number, index: number) => number' is not assignable to parameter of type '(value: number | null, index: number, array: (number | null)[]) => number'.
Types of parameters 'value' and 'value' are incompatible.
Type 'number | null' is not assignable to type 'number'.
Type 'null' is not assignable to type 'number'

我不知道如何“说服”TypeScript valueroasArray[index] 实际上都是数字而不是空值。

完整代码如下:

interface DailyData {
  id: number
  clientCost:number
  conValue:number
  clientConvValue: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 avgRoasDiscrepancy = (advertiserStats: DailyData[]) => {
  const clientRoasArray = advertiserStats.map((obj: DailyData) => obj.clientCost > 0 ? obj.clientConvValue/obj.clientCost: null);
  const roasArray = advertiserStats.map((obj: DailyData) => obj.clientCost > 0 ? obj.conValue/obj.clientCost: null);
  if (clientRoasArray.length === 0 || roasArray.length === 0) {
    return 'no data'
  }
  if (!!clientRoasArray.every((el: number|null) => el!== null) || (!roasArray.every((el: number|null) => el!== null) )) {
    //if one of the arrays has null values don't calculate
    return 'no data';
  }
  if (clientRoasArray.length === roasArray.length) {
    const discArray = clientRoasArray.map((value: number, index: number) => value - roasArray[index]);
    return calcAvg(discArray as number[]);
  }
  return 'no data';

};

在这一行

  const clientRoasArray = advertiserStats.map((obj: DailyData) => obj.clientCost > 0 ? obj.clientConvValue/obj.clientCost: null);

您假设 clientRoasArray 的元素可以是 number 也可以是 null,这在您的条件下很清楚 : null

并且在这一行中,您将 clientRoasArray 的值标识为 number only 这是错误的,因为您告诉您的代码该值可能是 number|null

您只需更改此行

const discArray = clientRoasArray.map((value: number, index: number) => value - roasArray[index]);

至此

const discArray = clientRoasArray.map((value: number | null, index: number) => value - roasArray[index]);

但是您将面临 roasArray[index] 的问题,因为它也可能是 null

我无法使用对数组的 every 调用使类型推断正常工作,但是您可以创建一个 type guard 来帮助编译器理解数组的类型number[] 而不是 (number | null)[]。这也有助于重构。

请注意,您当前的类型检查逻辑也存在问题,但看起来这只是一个拼写错误:第一个条件之前的 !! 应该只是一个 !.

这是一个简单的类型保护:

function isNonNullNumberArray(x: (number | null)[]): x is number[] {
    return x.every(e => e !== null);
}

然后你可以像这样使用isNonNullNumberArray:

  if (!(isNonNullNumberArray(clientRoasArray) && isNonNullNumberArray(roasArray))) {
    //if one of the arrays has null values don't calculate
    return 'no data';
  }
  // now the compiler knows that neither array contains null values
  if (clientRoasArray.length === roasArray.length) {
    const discArray = clientRoasArray.map((value: number, index: number) => value - roasArray[index]);
    return calcAvg(discArray);
  }
  return 'no data';

如果你想使用泛型,你也可以像这样创建一个更通用的类型保护,以便它适用于 number 以外的类型:

function isNonNullArray<T>(x: T[]): x is Exclude<T, null>[] {
    return x.every(e => e !== null);
}

顺便说一句,您不需要在传递给 map 和其他数组方法的函数中显式声明参数类型,因为编译器可以根据数组方法的签名推断类型(例如 map).

您可以检查您的变量是否不是 null 并使用 !(non-null 断言运算符)告诉 TypeScript 表达式不能是 nullvalue 的类型也是 number | null.

if (clientRoasArray.length === roasArray.length) {
  const discArray = clientRoasArray.map((value: number | null, index: number) => {
    return value && roasArray[index] ? value - roasArray[index]! : null
  });
  return calcAvg(discArray as number[]);
}