我有一个JSON文件,我想在typescript中使用,但不太确定如何定义类型/接口。
它基本上定义了虚拟键盘的布局。所以它会有一个layoutName,然后每行都有一个名字。
布局名称(如"default", "emoji")我想成为一个通用的字符串,可以是任何值,所以我在考虑记录类型。行名应该是一致的,但他们必须动态访问,所以我认为他们应该是记录了吗?
下面是JSON文件中的一个示例:
{
"default": {
"row1": [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
"-",
"=",
"Backspace"
],
"row1_shift": [
"!",
"@",
"#",
"$",
"%",
"^",
"&",
"*",
"(",
")",
"_",
"+",
"Backspace"
],
},
"emoji": {
"row1" : []
}
谢谢
所以我认为您确实希望对接口的顶级密钥使用string
索引签名。但是,对于下一个级别,如果您有一组已知的键,您可能应该使用它们,即使您必须动态访问属性。例如:
type RowNames = `row${1 | 2 | 3 | 4 | 5}${"" | "_shift"}`;
interface VirtualKeyboard {
[layoutName: string]: Partial<Record<RowNames, string[]>>;
}
相当于
/* type VirtualKeyboard = {
[x: string]: {
row1?: string[] | undefined;
row1_shift?: string[] | undefined;
row2?: string[] | undefined;
row2_shift?: string[] | undefined;
row3?: string[] | undefined;
row3_shift?: string[] | undefined;
row4?: string[] | undefined;
row4_shift?: string[] | undefined;
row5?: string[] | undefined;
row5_shift?: string[] | undefined;
};
} */
我只是使用模板文字类型来简洁地表达RowNames
,使用Partial<T>
和Record<K, V>
实用程序类型来表达子属性类型,而不重复。但这是一样的。
然后,当涉及到读取属性时,您可以这样做:
const virtualKeyboard: VirtualKeyboard = {/*...*/}
for (const layout in virtualKeyboard) {
for (const index of [1, 2, 3, 4, 5] as const) {
for (const modifier of ["", "_shift"] as const) {
let currentRow = virtualKeyboard[layout][`row${index}${modifier}`];
if (!currentRow) continue;
for (const key of currentRow) {
console.log(key.toUpperCase());
}
}
}
}
第一个循环遍历virtualKeyboard
的所有键。密钥layout
的类型是string
,使用它可以索引到virtualKeyboard
,因为它具有string
索引签名。对于下一个循环,我使用const
断言的索引和修饰符数组,以便编译器知道它们分别是1 | 2 | 3 | 4 | 5
和"" | "_shift"
类型。
`row${index}${modifier}`
来索引virtualKeyboard[layout]
。编译器能够将其视为模板文字类型(由于TS 4.3中的改进;在此之前,您可能还需要as const
),因此它会看到该键是子属性的已知键之一。
这意味着它知道currentRow
的类型是string[] | undefined
(由于定义中的Partial<>
,它可能是undefined
)。如果您知道它永远不会是undefined
,那么您可以省略Partial<>
)。因此,一旦我们检查了undefined
,我们就可以遍历它,编译器期望每个key
都是string
。
Playground链接到代码