typescript - 如何使它成为可扩展的打字稿类型?
问题描述
我有一个MeasurementData
看起来像这样的类型。
type MeasurementData = {
measurements: {
x: number | Date;
y: number;
}[];
xUnits: string;
xFirstValue: number;
xLastValue: number;
xSecondToLastValue: number;
xAverage: number;
xMin: number;
xMax: number;
xDelta: number;
yUnits: string;
yFirstValue: number;
yLastValue: number;
ySecondToLastValue: number;
yAverage: number;
yMin: number;
yMax: number;
yDelta: number;
};
但是现在该measurements
部分将扩展为可能未知数量的y
值。
例如
measurements: {
x: number | Date;
y0: number;
y1: number;
y2: number;
y3: number;
y4: number;
y5: number;
}[];
作为打字稿的新手,我不确定如何扩展MeasurementData
以处理这种情况。
你是如何做到的,这样你就不会重复所有的 y 计算定义?
例如 - 我不想这样做。
y0Units: string;
y0FirstValue: number;
y0LastValue: number;
y0SecondToLastValue: number;
y0Average: number;
y0Min: number;
y0Max: number;
y0Delta: number;
y1Units: string;
y1FirstValue: number;
y1LastValue: number;
y1SecondToLastValue: number;
y1Average: number;
y1Min: number;
y1Max: number;
y1Delta: number;
y2Units: string;
y2FirstValue: number;
y2LastValue: number;
y2SecondToLastValue: number;
y2Average: number;
y2Min: number;
y2Max: number;
y2Delta: number;
/// y3, y4, y5 ...etc...
解决方案
呃,这是一种噩梦类型。这几乎是可能的,但我请求你重构你的数据类型以使用数组,而不是越来越长的属性键列表,键名中包含数字。但我喜欢让 TypeScript 做一些糟糕的事情,所以这里是:
您希望成为不同数量属性的结构的所有版本MeasurementData
的无限联合。y
喜欢MeasurementDataElement<0> | MeasurementDataElement<0 | 1> | MeasurementDataElement<0 | 1 | 2> | MeasurementDataElement<0 | 1 | 2 | 3> | ...
。
你不能真正做无限联合。您可以将其作为通用约束,也可以作为最多某个有限数的联合。我将做后者并选择,说,40
所以你可以支持y0
通过y39
。
因为这涉及字符串连接和递归,所以我将使用 TypeScript 4.1 beta。TypeScript 4.1 将支持模板文字类型和映射类型键扩充以及递归条件类型:
type CommonSuffixProps = {
Units: string;
FirstValue: number;
LastValue: number;
SecondToLastValue: number;
Min: number;
Max: number;
Delta: number;
}
type CommonPrefixes<N extends number> = 'x' | `y${N}`
type MeasurementDataElement<N extends number> = Pick<
{ measurements: { [K in CommonPrefixes<N>]: ('x' extends K ? Date : never) | number }[]; } &
{ [K in keyof CommonSuffixProps as `${CommonPrefixes<N>}${K}`]: CommonSuffixProps[K] },
'measurements' | `x${keyof CommonSuffixProps}` | (N extends any ? `y${N}${keyof CommonSuffixProps}` : never)
> extends infer O ? { [K in keyof O]: O[K] } : never;
type TuplesOfUnionsOfNumbers<N extends number, A extends number[] = []> =
N extends A['length'] ? A : TuplesOfUnionsOfNumbers<N, [...A, A[number] | A['length']]>;
type MeasurementDataForTuplesOfUnionsOfNumbers<VN extends { [k: number]: number }> = {
[K in keyof VN]: MeasurementDataElement<Extract<VN[K], number>>
}[number]
// only works up to maximum number of 40 (y0, y1, y2, y3, y4, ... y38, y39)
type MeasurementData = MeasurementDataForTuplesOfUnionsOfNumbers<TuplesOfUnionsOfNumbers<40>>
那是一团糟 ,我不确定是否值得详细解释。我将重点介绍:
您可能会看到这
CommonSuffixProps
看起来像是您想要添加到 and 等末尾x
的y0
内容y1
。CommonPrefixes<N>
类型0 | 1 | 2
变成_'x' | 'y0' | 'y1' | 'y2'
MeasurementDataElement<N>
类型变成0 | 1
你正在寻找的类型,它的属性measurements
在{x: number | Date, y0: number, y1: number}[]
哪里,它的其他属性看起来都像结合CommonPrefixes<0 | 1>
了CommonSuffixProps
.TuplesOfUnionsOfNumbers<N>
类型变成4
tuple 。[0, 0|1, 0|1|2, 0|1|2|3]
我正在使用它,所以我们可以将单个数字转换为我们想要的可能的属性集集40
的大列表。y
获取之前的
MeasurementDataForTuplesOfUnionsOfNumbers<T>
那些 tuples-of-unions-of-numbers 之一,并为您提供其中MeasurementDataElement<N>
所有的N
并集。最后,
MeasurementData
is justMeasurementDataForTuplesOfUnionsOfNumbers<TuplesOfUnionsOfNumbers<40>>
,或者我们正在寻找的类型。
使用 IntelliSense 检查这种类型非常烦人。假设我们MeasurementData
查看具有y1Min
属性但没有y2Min
属性的单个联合元素:
type Example = Extract<Exclude<MeasurementData, {y2Min: any}>, {y1Min: any}>;
/* type Example = {
measurements: {
x: number | Date;
y0: number;
y1: number;
}[];
xUnits: string;
xFirstValue: number;
xLastValue: number;
xSecondToLastValue: number;
xMin: number;
xMax: number;
xDelta: number;
y0Units: string;
y0FirstValue: number;
y0LastValue: number;
y0SecondToLastValue: number;
... 9 more ...;
y1Delta: number;
} */
我猜这看起来对吗?作为进一步的证据,让我们尝试对 type 的值进行一些测试MeasurementData
:
declare const md: MeasurementData;
if ("y23Min" in md) {
md.y14SecondToLastValue.toFixed(2);
for (let m of md.measurements) {
m.y19.toFixed(2);
}
}
通过测试 if y23Min
is in the value,编译器知道它是至少有 24 个y
坐标的联合元素之一,所以它会让你索引到,比如说,y14SecondToLastValue
或measurements[0].y19
。
万岁!
你去吧。再次,我恳请您不要使用这种类型。相反,我强烈建议您重构为对 TypeScript 更友好的数据结构。也许是这样的?
interface Friendly<Y extends CommonSuffixProps[]> {
measurements: { x: number, y: { [K in keyof Y]: number } }[],
axisProperties: { x: CommonSuffixProps, y: Y }
}
function acceptFriendly<Y extends CommonSuffixProps[]>(f: Friendly<[...Y]>) { }
declare const axisProps: CommonSuffixProps;
acceptFriendly({
measurements: [
{ x: 0, y: [1, 2, 3] },
{ x: 1, y: [2, 3, 4] }
],
axisProperties: {
x: axisProps,
y: [axisProps, axisProps, axisProps]
}
})
这是属性数量的通用约束y
,否则我们将更y3Min
改为类似axisProperties.y[3].Min
. 这也可以更容易地迭代,对吧?但最终是否要重构取决于您。即使你不想,我也不能真正推荐上面那种类型的科学怪人。请认为这是一个警示故事,而不是解决方案。
推荐阅读
- algorithm - 如何计算do while的时间复杂度?
- haskell - Lambda 函数在 Haskell 中的工作原理
- javascript - JS中通过生成器函数的最佳实践
- c - 如何定义一个从用户获取输入并将其存储到程序中进一步使用的变量的函数?
- colors - 在 Paint.net 中更改透明度值也会自动更改颜色
- python - 获取发送消息的频道的ID
- python - 基于分发过程中条件变化的Dataframe任务分发
- reactjs - Laravel API + React SPA,仅 http cookie 未设置
- css - flex box 什么是维护孩子周围空白空间的正确方法
- r - 以网格格式绘制脉冲响应函数