动态声明Typescript接口的属性



是否有一种简便的方法来声明具有许多遵循模式的属性的接口?在我的例子中,我要创建一个有30个数据点的图表。我的界面应该是这样的

interface BarData {
day1: number;
day2: number;
...
day30: number;
}

是否有一些符号允许我声明day*范围从1到30而不必全部写出来?

假设您已经可以使用BarData,但想要用更少的样板代码来编写它(不幸的是,代价是更令人困惑的代码),这里有一种方法:

type LessThan<N extends number, A extends number[] = []> =
N extends A['length'] ? A[number] : LessThan<N, [...A, A['length']]>;
type OneToThirty = Exclude<LessThan<31>, 0>;
/* type OneToThirty = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 
| 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 */
interface BarData extends Record<`day${OneToThirty}`, number> { }

LessThan<N>类型函数是一个尾递归条件类型,它接受一个非负整数文字类型N,并产生一个小于N的所有非负整数文字类型的并集。所以LessThan<5>0 | 1 | 2 | 3 | 4。它通过累积由前一个累加器值的length属性组成的元组类型来实现这一点。因此,[]的长度为0,[0]的长度为1,[0, 1]的长度为2,等等。

那么我们可以使用LessThan<31>来获得030之间的所有数字,然后使用Exclude<T, U>实用程序类型来排除0并获得从130的数字。

之后,我们通过模板文字类型将这些数字附加到字符串"day",使用Record<K, V>实用程序类型引用具有day*键且值类型为number的类型,最后将BarData定义为扩展该记录类型的interface

你可以确保它工作:

function foo(bar: BarData) {
bar.day14 = 3;
}

万岁!


但是…我有点怀疑你是否真的同意BarData。如果您在类型级别以编程方式描述键名,我认为您也希望在运行时以编程方式创建它们。但是编译器不知道for (let i=1; i<31; i++) {}会产生OneToThirty类型的i。它只会推断number。所以你会得到errors:

for (let i = 1; i <= 30; i++) {
bar[`day${i}`]++; // error!
// Element implicitly has an 'any' type because expression of type  
// '`day${number}`' can't be used to index type 'BarData'
}

除非你开始跳过更多的环:

for (let j: OneToThirty = 1; j <= 30; j = j + 1 as OneToThirty) {
bar[`day${j}`]++; // okay
}

这很好,但并不比另一种选择更类型安全,其中BarData有模式索引签名,您只需注意保持在边界内:

interface BarData {
[k: `day${number}`]: number;
}
function foo(bar: BarData) {
for (let i = 1; i <= 30; i++) {
bar[`day${i}`]++; // okay
}
}
在这种情况下,你还不如直接使用数组,因为它给你带来了很多好处:
interface BarData {
day: number[];
}
function foo(bar: BarData) {
for (let i = 1; i <= 30; i++) {
bar.day[i]++; // okay
}
}

这可能是最常规的方法。不过,如果你对原来的BarData定义感到满意,那么顶部的OneToThirty将实现你正在寻找的东西。

Playground链接到代码

在最近的TypeScript版本中,你可以通过一个有效日期后缀和模板文字类型的联合来做到这一点:

type DayNumbers = 1 | 2 | 3 | 4 | 5 | 6; // ...and so on
type BarData = {
[key in `day${DayNumbers}`]: number;
}

要求对象具有所有的天数:

// Works, it has all the required properties
const example1: BarData = {
day1: 1,
day2: 2,
day3: 3,
day4: 4,
day5: 5,
day6: 6,
};
// Doesn't work, it's missing `day6`:
const example2: BarData = { // Error: Property 'day6' is missing in type ... but required in type 'BarData'.(2741)
day1: 1,
day2: 2,
day3: 3,
day4: 4,
day5: 5,
};

操场上联系

如果你不需要它们全部,你可以使用?使它们成为可选的:

type DayNumbers = 1 | 2 | 3 | 4 | 5 | 6; // ...and so on
type BarData = {
[key in `day${DayNumbers}`]?: number;
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−^
}

…在这种情况下,上面的example2就可以了。

操场上联系

最新更新