带字符串索引器的类型化字典

时间:2018-10-31 09:51:00

标签: typescript

我尝试使用Typescript 3实现翻译功能。要使用的应用程序/视图的最终API应该如下所示:

const translatedPasswordLabel = translator.Get('loginform.labels.password');

我喜欢检查翻译键(字符串参数)编译器。我让这一部分使用这样的字符串文字类型:

export type TranslationKeys =
    | 'loginform.labels.password'
    | 'loginform.labels.username'
    | 'loginform.actions.submit';

现在,我被困在设置翻译存储库中以定义和访问此翻译。这是我尝试过的事情之一:

interface IDefaultTranslation {
    defaultText: string;
}

interface ITranslations {
    [key: TranslationKeys]: IDefaultTranslation;
}

const translations: ITranslations = {
    'loginform.labels.password': {defaultText: 'Password'},
    'loginform.labels.username': {defaultText: 'Username'},
    'loginform.actions.submit': {defaultText: 'Submit'},
};

我不喜欢的是我必须两次指定翻译键。我也不知道如何以完全经过编译器检查的方式访问它们。尝试过类似

function getText<T, K extends keyof T>(translations: T, key: K): IDefaultTranslation {
    return translations[key];
}

但是编译器在这里不喜欢IDefaultTranslation

基本上,我喜欢一种类型化的字典,其中的键是一个选中的字符串,而值是一个IDefaultTranslation对象。如果我在translations中拼错了翻译密钥,或者如果我完全错过了在translations中定义的translationKeys中的翻译,我也想得到编译器错误。不确定这部分是否可以完成。

这种字典,必须定义/设置给定集合的所有键。

我还考虑过用我刚刚定义“字典”并直接针对.get('key')函数的键对其进行导出/检查的方式来验证逻辑,但是我不知道该怎么做。

我尝试的另一件事是将它们存储在类型数组中。这行得通,但我看不到如何检查所有键是否已定义/推入数组的方法。

有什么想法如何使用固定键或导出键进行编译检查来设置这种类型的字典?

1 个答案:

答案 0 :(得分:0)

据我了解,您的要求是:

  1. 将键定义为字符串文字类型的并集
  2. 定义一个必须包含所有键的对象类型
  3. 定义一个接受此类对象并且仅接受该对象的键作为第二个参数的函数

数字1和2可以使用映射类型完成。可以使用keyof类型的运算符来完成数字3。

export type TranslationKeys =
    | 'loginform.labels.password'
    | 'loginform.labels.username'
    | 'loginform.actions.submit';

interface IDefaultTranslation {
    defaultText: string;
}

// TKeys will be the union of all keys 
type ITranslations<TKeys extends string> = { [P in TKeys]: IDefaultTranslation }

// If we miss a field or have a missing one we will get an errror here
const translations: ITranslations<TranslationKeys> = {
    'loginform.labels.password': {defaultText: 'Password'},
    'loginform.labels.username': {defaultText: 'Username'},
    'loginform.actions.submit': {defaultText: 'Submit'},
};


// Function that gets the text:
function getText<T extends ITranslations<any>>(translations: T, key: keyof T): IDefaultTranslation {
    return translations[key];
}

// We get an error if we call with a missing key
getText(translations, "loginform.actions.submit")

注意 ITranslations<TKey>基本上等同于内置类型Record<TKey, IDefaultTranslation>。保留专用名称以明确类型的语义是很有意义的。

您也可以有一个代表键入字典的类:

class TypedTranslation<TKeys extends string> {
    public constructor(private translations: ITranslations<TKeys>) {}
    Get(key: TKeys) : IDefaultTranslation {
        return this.translations[key];
    }
}
var t = new TypedTranslation({
    'loginform.labels.password': {defaultText: 'Password'},
    'loginform.labels.username': {defaultText: 'Username'},
    'loginform.actions.submit': {defaultText: 'Submit'},
});
t.Get("loginform.actions.submit")

修改

在我的上一个示例中,实际上是根据传入的对象来推断类TKeys的。您可以通过实例化TypedTranslation类并从中提取键来避免重新声明所有键。

type TypedTranslationKeys<T extends TypedTranslation<any>> = T extends TypedTranslation<infer Keys> ? Keys : never;

type keysForT = TypedTranslationKeys<typeof t> // will be "loginform.labels.password" | "loginform.labels.username" | "loginform.actions.submit"