是否有一种简便的方法来声明具有许多遵循模式的属性的接口?在我的例子中,我要创建一个有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>
来获得0
和30
之间的所有数字,然后使用Exclude<T, U>
实用程序类型来排除0
并获得从1
到30
的数字。
之后,我们通过模板文字类型将这些数字附加到字符串"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
就可以了。
操场上联系