我有代码,将图表的数据在时间线上.我想让它接收一系列的序列.每个系列包含数据数组和几个将应用于每个数据项以在图表上定位和呈现数据项的获取方法.

以下是一些经过高度简化的示例代码.在DataSeries<T>上会有多个Getter,渲染将使用Reaction完成,但这与我面临的问题无关.

export interface DataSeries<T> {
  data: T[];
  asNumber: (d: T) => number;
}

export function DataSeriesAsNumbers<T>(series: DataSeries<T>[]) {
  series.forEach((s) => {
    s.data.forEach((d) => {
      console.log(s.asNumber(d).toFixed(0));
    });
  });
}

在使用该函数时,我希望根据附带的data属性中给出的数组正确地推断出getNumber个回调的参数.

DataSeriesAsNumbers([
  {
    data: [
      { val: 1, seq: 12346 },
      { val: 2, seq: 12347 },
    ],
    asNumber: (d) => d.val,
  },
  {
    data: [
      { amount: 6, type: 5 },
      { amount: 20, type: 4 },
    ],
    asNumber: (d) => d.amount,
  },
]);

到目前为止,我一直无法做到这一点.我通过手动分配类型(使用assatisfies或通过单独创建类型化的系列对象)或只使用DataSeriesAsNumbers<T = any>将类型抛出窗口来使其工作.

问题似乎源于series: DataSeries<T>[],所以一旦您在不同的data属性中混合了不同的对象,所有的获取器现在都必须坚持整个Union类型.上面的代码导致编译器将联合类型分配给d参数:

ts error

我曾研究过使用infer这个关键字,但也没有弄明白这一点.我在网上看过语法[...T],但同样没有做到.

是否可以根据数据数组自动推断回调参数的类型?

推荐答案

你可以写a mapped type over an array/tuple type,就像这样:

function RenderDataChart<T extends any[]>(
  series: [...{ [I in keyof T]: DataSeries<T[I]> }]
) {
  series.forEach(<TI,>(s: DataSeries<TI>) => {
    s.data.forEach((d) => {
      const x = s.getX(d);
      const y = s.getY(d);
      const color = s.getColor(d);
      render(x, y, color, d) // render logic
    });
  })
}

映射类型{[I in keyof T]: DataSeries<T[I]>}实质上取tuple typeT,并用DataSeries<>"包装"每个元素T[I].因此,如果T[X, Y, Z],则映射的类型是[DataSeries<X>, DataSeries<Y>, DataSeries<Z>].并且幸运的是,编译器能够从映射类型{[I in keyof T]: DataSeries<T[I]>}中提取infer T.

请注意,我将映射类型包装在variadic tuple type[...+]中.这只是给了编译器一个提示,它应该将数组文字的类型推断为元组类型,而不是规则array.也就是说,您希望它将您的series输入看作像[DataSeries<X>, DataSeries<Y>, DataSeries<Z>]这样的类型,而不是像(DataSeries<X> | DataSeries<Y> | DataSeries<Z>)[]这样可能表现不佳的类型.


这是基本的方法,但不幸的是,在从映射类型进行推断时,TypeScrip不能contextually inferd个回调参数的类型.microsoft/TypeScript#53018有一个开放的功能请求:

RenderDataChart([
  {
    data: [{ val: 1, seq: 12346 }, { val: 2, seq: 12347 }],
    getX: (d) => d.val, // err
    getY: (d) => d.seq, // err
    getColor: (d) => d.val === 0 ? "red" : "blue", // err
  },
  {
    data: [{ amount: 6, type: 5 }, { amount: 20, type: 4 }],
    getX: (d) => d.amount, // err
    getY: (d) => d.type, // err
    getColor: () => "green", 
  },
]);
// T inferred as [{ val: number; seq: number;}, { amount: number; type: number;}]>

除非有解决方法,否则您将需要一个generic帮助函数来帮助进行回调参数推断.像这样的东西

const h = <T,>(x: DataSeries<T>) => x;

应该可以工作,如下所示:

RenderDataChart([
  h({
    data: [{ val: 1, seq: 12346 }, { val: 2, seq: 12347 }],
    getX: (d) => d.val,
    getY: (d) => d.seq,
    getColor: (d) => d.val === 0 ? "red" : "blue",
  }),
  h({
    data: [{ amount: 6, type: 5 }, { amount: 20, type: 4 }],
    getX: (d) => d.amount,
    getY: (d) => d.type,
    getColor: () => "green",
  }),
]);
// T inferred as [{ val: number; seq: number;}, { amount: number; type: number;}]>

现在一切都按预期进行了.类型自变量T被推断为元组类型,其中每个元素对应于series的每个元素,并且回调参数d被上下文类型化为该元素类型.

Playground link to code

Typescript相关问答推荐

当类型断言函数返回假时,TypScript正在推断从不类型

用泛型类型覆盖父类函数导致类型y中的Property x不能赋给基类型Parent中的相同属性."'''''' "

使用带有RxJS主题的服务在Angular中的组件之间传递数据

无法将select选项的值设置为ngFor循环中的第一个元素

React typescribe Jest调用函数

如何在TypeScrip中使用嵌套对象中的类型映射?

类型脚本基于数组作为泛型参数展开类型

我想创建一个只需要一个未定义属性的打字脚本类型

为什么T[数字]不也返回UNDEFINED?

TypeScrip:来自先前参数的参数中的计算(computed)属性名称

如何合并两个泛型对象并获得完整的类型安全结果?

为什么我的导航在使用Typescript的React Native中不起作用?

如何缩小函数的返回类型?

如何避免从引用<;字符串&>到引用<;字符串|未定义&>;的不安全赋值?

TypeScrip-根据一个参数值验证另一个参数值

设置不允许相同类型的多个参数的线性规则

埃斯林特警告危险使用&as";

typescript如何从映射类型推断

使用本机 Promise 覆盖 WinJS Promise 作为 Chrome 扩展内容脚本?

通过函数将对象文字类型映射到另一种类型的方法或解决方法