嵌套对象的打字稿字符串点表示法

2024-04-13

我有一个翻译字符串的嵌套对象,如下所示:

viewName: {
    componentName: {
        title: 'translated title'
    }
}

我使用接受点表示法字符串的翻译库来获取字符串,如下所示translate('viewName.componentName.title').

有什么方法可以强制翻译的输入参数遵循打字稿对象的形状?

我可以通过这样做来完成第一级:

translate(id: keyof typeof languageObject) {
    return translate(id)
}

但我希望这种类型是嵌套的,以便我可以像上面的示例一样调整我的翻译范围。


TS4.1 的更新。现在可以通过以下方式在类型级别表示字符串连接模板字符串类型,实施于微软/TypeScript#40336 https://github.com/microsoft/TypeScript/pull/40336。现在您可以获取一个对象并在类型系统中获取其虚线路径。

Imagine languageObject这是:

const languageObject = {
    viewName: {
        componentName: {
            title: 'translated title'
        }
    },
    anotherName: "thisString",
    somethingElse: {
        foo: { bar: { baz: 123, qux: "456" } }
    }
}

首先我们可以使用递归条件类型如实施于微软/TypeScript#40002 https://github.com/microsoft/TypeScript/pull/40002 and 可变元组类型如实施于微软/TypeScript#39094 https://github.com/microsoft/TypeScript/pull/39094将对象类型转换为与其对应的键元组的并集string- 有价值的属性:

type PathsToStringProps<T> = T extends string ? [] : {
    [K in Extract<keyof T, string>]: [K, ...PathsToStringProps<T[K]>]
}[Extract<keyof T, string>];

然后我们可以使用模板字符串类型将字符串文字元组连接到点分路径(或任何分隔符D:)

type Join<T extends string[], D extends string> =
    T extends [] ? never :
    T extends [infer F] ? F :
    T extends [infer F, ...infer R] ?
    F extends string ? 
    `${F}${D}${Join<Extract<R, string[]>, D>}` : never : string;    

结合这些,我们得到:

type DottedLanguageObjectStringPaths = Join<PathsToStringProps<typeof languageObject>, ".">
/* type DottedLanguageObjectStringPaths = "anotherName" | "viewName.componentName.title" | 
      "somethingElse.foo.bar.qux" */

然后可以在签名中使用translate():

declare function translate(dottedString: DottedLanguageObjectStringPaths): string;

我们得到了我三年前谈论的神奇行为:

translate('viewName.componentName.title'); // okay
translate('view.componentName.title'); // error
translate('viewName.component.title'); // error
translate('viewName.componentName'); // error

Amazing!

Playground 代码链接 https://www.typescriptlang.org/play?ts=4.1.2#code/MYewdgzgLgBANgQzAcwK4OQUwPICMBWmwsAvDAN4CwAUDHTAG4CWmA7gHIIC2mAXBTXpCYoLgAdwmMFE49+VWsKVQmUOHxgByKACckERFEwATGCrWZNgpTAC+1urYA0DmEhBQAFph2yNAIi8mCABlXSYUfxdFOggQHiCUAFE4CA0FGxgAMxAQeRhcBB18woAvfgBGACYAZicYAEdUAA9+fwAWAFYANn87O1d7aiGaKABPMUwYAAUELwgAFRAwnQjkaZ0QMQgAHgWAPhgyBZhMZqMwYwgYaFWUGAB+GABtAF0YeVdngGkYCJgkuc9MQdgBrTBjEBZGALeq3Nb7V78H71AB06Nm8yWKzWGy2uwWP1eiJotmegN0CBB4Mh0NhN3CKERAG4aKMJlMAFIgCJ7U7nKRXBl3ZBveoAEX5FyF8KZR1cJzO0uub0eMDAmAYPg+CqlgpVESy2oAYu8nsadTEYXrLgawEadDBjWj0YbtQAlM2uC1K-XCtaPVwAAwAJORjbYw+LI+RubyKcCoDt3XDGaLiRL9rYgx91ZrtfxZchWdR2ZMYOKPEZjAAZJBoDA4AhEKA4lCYzzXMhxsA7DuLZZpvHbHbjSZQ+D19BYPCEYj7er+VH+fY0AD0ACozByK1WTHWUNOm3PW0O5p2jjB-O4vD4-H0AD5X5hsPyo0QSDXSN-mdSPmCuHQ-hxAknhrCkaSojkICooUOiok0zR9Bua5stQxhEIgOhTFkqBgMQTDgGYeiQIYmAABTGHuxhtsg-CVlA1YHg2M7NsQtH9gAlIWaYlqMJEGHMFGaC+HDcJg77xJ+UgyOJqK-pYnHMjAa5rjAICgggYz8foZHkSJLCsJJ4iSN+ckKZoSkqWpPibDoOmkUJ+miW+H6mVA8mqOolnKappw6HZDmCUYzmGa5UnuX4PnWf5gWltQQA


TS4.1 之前的答案:

如果你想让 TypeScript 帮助你,你就必须帮助 TypeScript。它不知道有关连接字符串文字的类型的任何信息,因此这是行不通的。我对如何帮助 TypeScript 的建议可能比您想要的更多,但它确实带来了一些相当不错的类型安全保证:


首先,我假设你有一个languageObject and a translate()知道它的函数(意味着languageObject大概是用来生产特定的translate()功能)。这translate()函数需要一个点分字符串,表示嵌套属性的键列表,其中最后一个此类属性是string-valued.

const languageObject = {
  viewName: {
    componentName: {
      title: 'translated title'
    }
  }
}
// knows about languageObject somehow
declare function translate(dottedString: string): string;
translate('viewName.componentName.title'); // good
translate('view.componentName.title'); // bad first component
translate('viewName.component.title'); // bad second component
translate('viewName.componentName'); // bad, not a string

介绍Translator<T>班级。您可以通过给它一个对象和一个来创建它translate()该对象的函数,然后您调用它get()链中的方法来深入了解密钥。当前值T始终指向您通过链选择的财产类型get()方法。最后,你打电话translate()当你到达string您关心的价值。

class Translator<T> {
  constructor(public object: T, public translator: (dottedString: string)=>string, public dottedString: string="") {}

  get<K extends keyof T>(k: K): Translator<T[K]> {    
    const prefix = this.dottedString ? this.dottedString+"." : ""
    return new Translator(this.object[k], this.translator, prefix+k);
  }

  // can only call translate() if T is a string
  translate(this: Translator<string>): string {
    if (typeof this.object !== 'string') {
      throw new Error("You are translating something that isn't a string, silly");
    }
    // now we know that T is string
    console.log("Calling translator on \"" + this.dottedString + "\"");
    return this.translator(this.dottedString);
  }
}
    

初始化它languageObjecttranslate()功能:

const translator = new Translator(languageObject, translate);

并使用它。这按预期工作:

const translatedTitle = translator.get("viewName").get("componentName").get("title").translate();
// logs: calling translate() on "viewName.componentName.title"

这些都会根据需要产生编译器错误:

const badFirstComponent = translator.get("view").get("componentName").get("title").translate(); 
const badSecondComponent = translator.get("viewName").get("component").get("title").translate(); 
const notAString = translator.get("viewName").translate();

希望有帮助。祝你好运!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

嵌套对象的打字稿字符串点表示法 的相关文章

随机推荐