假设我想在打字稿中创建一个包含多个项目的对象,如下所示:
const obj: Items = {
item1: 'foo',
item2: 'bar',
item3: 'baz',
}
我应该如何声明我的 Items 类型以使其与任意数量的项目兼容?我使用 Typescript 4.1 中的模板文字尝试了以下操作,但似乎不起作用:
interface Items {
[P: `array${number}`]: any;
}
是否可以声明这样的类型?
答案 0 :(得分:8)
TypeScript 4.4 将支持包含模式模板文字的索引签名,如在 microsoft/TypeScript#44512 中实现的那样。然后您就可以将 Items
声明为特定类型,如下所示:
interface Items {
[key: `item${number}`]: any;
}
您可以验证它是否按预期工作:
const obj: Items = {
item1: 'foo',
item2: 'bar',
item2021: 'baz',
item3: 'qux',
};
const objBad: Items = {
item1: 'foo',
item2: 'bar',
itemMMXXI: 'baz', // error!
// ~~~~~~~~~ <--
// Object literal may only specify known properties,
// and 'itemMMXXI' does not exist in type 'Items'
item3: 'qux'
};
从 TypeScript 4.1 开始,目前不允许将 `item${number}`
形式的模式模板文字(在 microsoft/TypeScript#40598 中实现)作为键类型。
目前没有与您想要的 Items
类型相对应的特定类型。相反,您可以将其表示为类型上的约束,并编写一个仅接受遵守约束的输入的辅助函数 asItems()
:
const asItems = <K extends PropertyKey>(
obj: { [P in K]: P extends `item${number}` ? any : never }
) => obj;
将检查传入的 obj
的每个键是否可分配给 `item${number}`
。如果是,则属性类型为any
,否则,属性类型为never
。这往往会导致任何违规属性出现错误:
const obj = asItems({
item1: 'foo',
item2: 'bar',
item2021: 'baz',
item3: 'qux',
}); // okay
const objBad = asItems({
item1: 'foo',
item2: 'bar',
itemMMXXI: 'baz', // error!
// ~~~~~~~~~ <-- Type 'string' is not assignable to type 'never'
item3: 'qux'
});
答案 1 :(得分:1)
我最终采用了一种方法,首先构造一个固定长度的元组(因为它的键不能无限长),然后在过滤掉非数字键的同时遍历它们,并用它来构造 Items 类型。然而,如上所述,一个警告是数量不能超过限制(恰好是 44),但对于我的用例来说已经足够了,所以我很满意。
// https://github.com/Microsoft/TypeScript/issues/26223#issuecomment-513116547
type PushFront<TailT extends any[], FrontT> = ((...args: [FrontT, ...TailT]) => any) extends (...tuple: infer TupleT) => any ? TupleT : never;
type Tuple<ElementT, LengthT extends number, OutputT extends any[] = []> = {
0: OutputT;
1: Tuple<ElementT, LengthT, PushFront<OutputT, ElementT>>;
}[OutputT['length'] extends LengthT ? 0 : 1];
const N = 44;
// N larger than 44 seems to exceed recursion limit
// const N = 45;
type NameN<Name extends string, T> = {
[P in keyof Tuple<any, typeof N> as P extends `${number}` ? `${Name}${P}` : never]: T;
};
type Items = NameN<'item', any>
答案 2 :(得分:1)
如果您可以接受的属性数量有一个合理的最大值,您可能只想让编译器为您计算这些属性。例如,以下将通过 item1
生成 item249
:
type Foo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 10
type Bar = [...Foo, ...Foo, ...Foo, ...Foo, ...Foo]; // 50
type Baz = [...Bar, ...Bar, ...Bar, ...Bar, ...Bar]; // 250
type ItemNames = `item${Exclude<keyof Baz, '0' | keyof any[]>}`
// item1 through item249
type ItemProps = { [K in ItemNames]?: any };
interface Items extends ItemProps {
}
const obj: Items = {
item1: 'foo',
item2: 'bar',
item3: 'baz',
} // okay
const objBad: Items = {
item1: 'foo',
item2: 'bar',
itemMMXXI: 'baz', // error!
//~~~~~~~~~~~~~ <-- Object literal may only specify known properties, and 'itemMMXXI'
// does not exist in type Items
};
在编译器开始抱怨之前,您可以将其扩展到小于 10,000 的最大值:
type Foo = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // 10
type Bar = [...Foo, ...Foo, ...Foo, ...Foo, ...Foo]; // 50
type Baz = [...Bar, ...Bar, ...Bar, ...Bar, ...Bar]; // 250
type Qux = [...Baz, ...Baz, ...Baz, ...Baz, ...Baz]; // 1250
type Quux = [...Qux, ...Qux, ...Qux, ...Qux, ...Qux]; // 6250
// type Suux = [...Quux, ...Quux]; // error:
// Type produces a tuple type that is too large to represent.
type ItemNames = `item${Exclude<keyof Quux, '0' | keyof any[]>}`
// item1 through item6249
答案 3 :(得分:0)
见https://basarat.gitbook.io/typescript/type-system/index-signatures
你很接近
interface MyInterface {
[key: string]: string;
}
const x: MyInterface = {
item1: "string"
}
答案 4 :(得分:0)
不可能完全这样声明类型。 Danie A 的答案是您真正能得到的最接近的答案,但是您将接受所有字符串。如果您知道您需要的数字数量有上限并且您愿意将它们全部输入,您可以执行以下操作:
map!(a->a>5 ? 5 : a, x, x)
我相信你已经知道你可以做到这一点。
答案 5 :(得分:0)
这是我的解决方案:
type Item<n extends number> = `item${n}`
type Items<n extends number[]> = {
[key in Item<n[number]>]: any
}
const goodObj: Items<[1, 2, 2021, 3]> = {
item1: 'foo',
item2: 'bar',
item2021: 'baz',
item3: 'qux',
}; // okay
const badObj: Items<[1, 2, 2021, 3]> = {
item1: 'foo',
item2: 'bar',
itemMMXXI: 'baz', // error!
// ~~~~~~~~~ <-- Type '{ item1: string; item2: string; itemMMXXI: string; item3: string; }' is not assignable to type 'Items<[1, 2, 2021, 3]>'.
// Object literal may only specify known properties, and 'itemMMXXI' does not exist in type 'Items<[1, 2, 2021, 3]>'
item3: 'qux'
};