我正在尝试创建一个通用接口来定义翻译字典,其中主键指定语言,并且在每种语言中都存在相同的键。
这是我迄今为止使用泛型的尝试(最好):
interface TranslationDict<K extends string, O extends K[number]> {
eng: Record<K, string>;
jpn: Record<O, string>;
}
const translationDict: TranslationDict<string, string> = {
eng: {
year: "year",
month: "month",
day: "day",
file: "file",
},
jpn: {
year: "年",
month: "月",
day: "日",
file: "ファイル",
file2: "bla", // I want this to throw a type error
},
};
没有泛型,这可以工作,但似乎太多样板:
const cols = ["year", "month", "day", "file"] as const;
export const commonCols: {
eng: Record<(typeof cols)[number], string>;
jpn: Record<(typeof cols)[number], string>;
} = {
eng: {
year: "year",
month: "month",
day: "day",
file: "file",
},
jpn: {
year: "年",
month: "月",
day: "日",
file: "ファイル",
file2: "bla", // TYPE ERROR
},
};
如何使用泛型来约束
jpn
的键与 eng
的键相同?
您确实想使用 generic 类型,例如
TranslationDict<K>
:
interface TranslationDict<K extends string> {
eng: Record<K, string>,
jpn: Record<K, string>
}
但是当你注释类型时,你需要手动写出类型参数:
const dict: TranslationDict<"year" | "month" | "day" | "file"> = {
eng: {
year: "year",
month: "month",
day: "day",
file: "file",
},
jpn: {
year: "年",
month: "月",
day: "日",
file: "ファイル",
file2: "bla", // error!
//~~~ <-- Object literal may only specify known properties
// but 'file2' does not exist in type
// 'Record<"year" | "month" | "day" | "file", string>'
},
};
这给了你你想要的 excess property 错误,但它是多余的,因为你被迫自己写
"year" | "month" | "day" | "file"
。你真的希望编译器从 eng
推断,也许像
// don't write this, not valid
const dict: TranslationDict<infer> = { ⋯ };
但目前不支持。您无法推断泛型 type 的类型参数。 microsoft/TypeScript#32794 有一个功能请求,但在该功能实现之前,您需要另一种方法。
获得此类行为的最常见方法是编写一个通用的函数来提供帮助。当您调用泛型函数时,TypeScript 确实会推断泛型类型参数,因此我们可以使用该推断。辅助函数只是运行时的一个类似于
d => d
的标识。它的唯一目的是帮助推理。
像这样:
const translationDict = <K extends string>(
d: TranslationDict<K>) => d;
然后调用该函数而不是注释:
const dict = translationDict({
eng: { // error!
year: "year",
month: "month",
day: "day",
file: "file",
},
jpn: {
year: "年",
month: "月",
day: "日",
file: "ファイル",
file2: "bla",
},
});
这可行,但错误不在您想要的位置。编译器最终将
K
推断为 "year" | "month" | "day" | "file" | "file2"
,然后抱怨 eng
是错误的。也许这足以满足您的需求。
但是,如果您希望编译器仅从
K
推断 eng
,然后仅针对 K
check
jpn
,您可以使用 NoInfer
实用程序类型 来阻止从 的话语进行推断
K
属性中的 jpn
。即,将TranslationDict<K>
更改为
interface TranslationDict<K extends string> {
eng: Record<K, string>,
jpn: Record<NoInfer<K>, string>
}
一旦执行此操作,您最终将获得指定的所需行为:
const dict = translationDict({
eng: {
year: "year",
month: "month",
day: "day",
file: "file",
},
jpn: {
year: "年",
month: "月",
day: "日",
file: "ファイル",
file2: "bla", // error!
//~~~ <-- Object literal may only specify known properties
// but 'file2' does not exist in type
// 'Record<"year" | "month" | "day" | "file", string>'
},
});
现在
K
被推断为 "year" | "month" | "day" | "file"
,因此对象字面量的 eng
属性很好,但是 jpn
有多余的 file2
属性。