我正在尝试编写一种表达以下内容的类型,而不需要显式地为翻译字典创建一个类型。
type Language = "english" | "german" | "french";
const translations = {
english: {
foo: "foo",
bar: "bar"
},
german: {
foo: "föö"
}
};我想出了这个
type Translations = {english: T, } & {[key in Language]?: Partial<T>};但是这并不完全满足我的要求,因为T仍然需要传递给类型参数.
发布于 2021-05-09 03:16:27
TypeScript不能将Translations表示为特定类型。您需要的是一种大多数泛型语言不具备的不同类型的泛型类型:所谓的存在量化泛型类型,或者有时只是存在型类型。在您的Translations类型中,有一个类型参数T.但你不想指定它。相反,您想要说的是,“应该允许提供Translations值的人将T指定给他们想要的任何东西;我所关心或知道的就是存在这样一个T。”也许这样的类型定义应该如下所示:
/* NOT SUPPORTED
type Translations = <exists T extends Record<keyof T, string>>(
{ english: T } & { [K in Language]?: Partial<T> }
);
*/但当然,你不能这么做。在exists中没有TypeScript关键字,也没有以这种方式量化泛型类型参数的直接方法。微软/打字稿#14466上有一个关于存在类型的特性请求,但是还不清楚该特性是否会被实现。
有一些方法可以在TypeScript中模拟存在类型,但它们有点复杂,使用起来也有些奇怪。与其尝试这样做,不如放弃存在类型,转而考虑可能的解决办法。
最简单的解决方法是将Translations<T>定义为类型上的泛型约束,并使用类型参数T。
type Translations<T extends Record<keyof T, string>> =
{ english: T } & { [K in Language]?: Partial<T> };为了避免要求用户在类型注释中指定T,您可以创建一个助手函数:
const asTranslations = <T extends Record<keyof T, string>>(
translations: Translations<T>) => translations;这个标识函数只返回它的输入,所以在运行时它本质上是一个不操作。但是,通过将输入限制为Translations<T>,我们要求编译器从输入中推断出泛型类型T。如果调用编译时没有错误,那么我们知道输入是有效的:
const translations = asTranslations({
english: {
foo: "foo",
bar: "bar"
},
german: {
foo: "föö"
}
}); // okay
/* const translations: Translations<{
foo: string;
bar: string;
}> */在这里,编译器推断T是{foo: string, bar: string},因此translations被推断为Translations<{foo: string, bar: string}>类型。
如果您犯了错误,编译器将警告您:
const badTranslations = asTranslations({
english: {
foo: "foo",
bar: "bar"
},
german: {
foo: "föö",
baz: "baß" // error!
// ~~~~~~~~~~
// Type '{ foo: string; baz: string; }' is not assignable
// to type Partial<{ foo: string; bar: string; }>'
// Object literal may only specify known properties, and 'baz'
// does not exist in type 'Partial<{ foo: string; bar: string; }>'
}
});在这种情况下,编译器也会推断{foo: string; bar: string}用于T。但这一次,在超额财产警告的baz属性上有一个baz,因为baz既不是foo也不是bar。
(请注意,过多的属性警告只发生在对象文本中。如果需要在调用asTranslations()之前将对象文本复制到变量中,则不会收到警告。如果这个用例很重要,您可以切换到Translations<T>的另一个定义,但是除非有一些需要,否则我不会在这里讨论这个问题。它在下面的链接游乐场代码示例中。)
此解决方案的一个副作用是,您将需要对Translations<T>类型本身进行任何处理。您可以让最终用户不必指定T,但是您的代码库最终仍然会拖曳一个额外的类型参数。例如,一个理想中看起来像function saveTranslations(translations: Translations, filename: string): void {}的函数现在看起来像function saveTranslations<T extends Record<keyof T, string>>(translations: Translations<T>, filename: string): void {}了。
因此,最好在接受输入的验证函数中只需要这样一个泛型类型,然后在内部库代码中将类型扩展到一些不太准确但更易于使用的内容:
function userFacingFunction<T extends Record<keyof T, string>>(
translations: Translations<T>) {
internalLibraryFunction(translations);
}
type AnyTranslations = Translations<Record<string, string>>;
// not generic anymore
function internalLibraryFunction(translations: AnyTranslations) {
// do something
}https://stackoverflow.com/questions/67453387
复制相似问题