嵌套对象的打字稿字符串点表示法 [英] Typescript string dot notation of nested object

查看:25
本文介绍了嵌套对象的打字稿字符串点表示法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a nested object of translation strings like so:

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

我使用一个接受点符号字符串的翻译库来获取字符串,就像这样translate('viewName.componentName.title').

I use a translation library that accepts strings in dot notation to get strings, like so translate('viewName.componentName.title').

有什么办法可以强制translate的输入参数跟随打字稿对象的形状吗?

Is there any way I can force the input parameter of translate to follow the shape of the object with typescript?

我可以这样做:

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

但我希望这种类型是嵌套的,这样我就可以像上面的例子一样确定我的翻译范围.

But I would like this typing to be nested so that I can scope my translations like in the example above.

推荐答案

针对 TS4.1 的更新.字符串连接现在可以通过模板字符串类型在类型级别表示,在微软/TypeScript#40336.现在你可以在类型系统中获取一个对象并获取它的虚线路径.

UPDATE for TS4.1. String concatenation can now be represented at the type level through template string types, implemented in microsoft/TypeScript#40336. Now you can take an object and get its dotted paths right in the type system.

想象一下languageObject是这样的:

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

首先,我们可以使用递归条件类型,如microsoft/TypeScript 中实现的那样#40002可变元组类型,在 microsoft/TypeScript 中实现#39094 将对象类型转换为与其 string 值属性相对应的键元组的联合:

First we can use recursive conditional types as implemented in microsoft/TypeScript#40002 and variadic tuple types as implemented in microsoft/TypeScript#39094 to turn an object type into a union of tuples of keys corresponding to its string-valued properties:

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

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

And then we can use template string types to join a tuple of string literals into a dotted path (or any delimiter 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() 的签名中使用:

which can then be used inside the signature for translate():

declare function translate(dottedString: DottedLanguageObjectStringPaths): string;

我们得到了我三年前所说的神奇行为:

And we get the magical behavior I was talking about three years ago:

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

太棒了!

代码的游乐场链接

TS4.1 之前的答案:

Pre-TS4.1 answer:

如果你想让 TypeScript 帮助你,你必须帮助 TypeScript.它对连接字符串文字的类型一无所知,因此无法正常工作.我关于如何帮助 TypeScript 的建议可能比您想要的要多,但它确实会带来一些相当不错的类型安全保证:

If you want TypeScript to help you, you have to help TypeScript. It doesn't know anything about the types of concatenated string literals, so that won't work. My suggestion for how to help TypeScript might be more work than you'd like, but it does lead to some fairly decent type safety guarantees:

首先,我将假设您有一个 languageObject 和一个知道它的 translate() 函数(意味着 languageObject大概用于生成特定的 translate() 函数).translate() 函数需要一个表示嵌套属性键列表的虚线字符串,其中最后一个这样的属性是 string 值.

First, I'm going to assume you have a languageObject and a translate() function that knows about it (meaning that languageObject was presumably used to produce the particular translate() function). The translate() function expects a dotted string representing list of keys of nested properties where the last such property is 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 类.您可以通过给它一个对象和该对象的 translate() 函数来创建一个,然后在链中调用它的 get() 方法以深入了解键.T 的当前值始终指向您通过 get() 方法链选择的属性类型.最后,当您达到您关心的 string 值时,您调用 translate().

Introducing the Translator<T> class. You create one by giving it an object and a translate() function for that object, and you call its get() method in a chain to drill down into the keys. The current value of T always points to the type of property you've selected via the chain of get() methods. Finally, you call translate() when you've reached the string value you care about.

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() 函数初始化它:

Initialize it with languageObject and the translate() function:

const translator = new Translator(languageObject, translate);

并使用它.这可以根据需要工作:

And use it. This works, as desired:

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

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

And these all produce compiler errors, as desired:

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();


希望有所帮助.祝你好运!


Hope that helps. Good luck!

这篇关于嵌套对象的打字稿字符串点表示法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆