Typescript 中变量的动态字符串文字类型

Dynamic String Literal Type from Variable in Typescript

我正在从身份验证提供商那里获得一个带有声明的令牌。这是一个 JSON 对象,如下所示:

{
  "email": "user@example.org",
  "urn:dev:claims/user-id": "123"
}

我正在尝试在我的客户端上创建一个代表性界面或类型以正确访问此令牌密钥。问题是上面“urn:dev:claims/user-id”的“dev”部分是动态的,它来自这样的环境变量:

const claimKey = urn:${process.env.REACT_APP_ENV}:claims/user-id

当我尝试以下操作时:

interface MyInterface {
  email: string;
  [claimKey]: string;
}

没用。

这是一个完整的可重现示例:

const env = {
  REACT_APP_ENV: "dynamic-value"
}

const token = {
  "email": "user@example.org",
  "urn:dev:claims/user-id": "123"
}

const claimKey = `urn:${env.REACT_APP_ENV}:claims/user-id`

interface MyInterface {
  email: string;
  [claimKey]: string;
}

TypeScript 目前无法完全按照您的要求进行操作:

  • 您希望编译器将 process.env.REACT_APP_ENV 视为一些“未知但唯一”的 string 字面值,就像 unique symbol works for symbol-typed values. There was an experimental pull request at microsoft/TypeScript#33038 允许的方式一样unique string,但它从未成为语言。

  • 此外,您需要能够 将该唯一字符串连接 到其他字符串文字并具有某种唯一输出;也许这还需要在 microsoft/TypeScript#40598 中实现的“模式”模板文字类型中支持 unique string,而且这是否可行尚不明显。

  • 即使所有这些都已解决,您目前也不能将模式模板文字类型用作对象键;参见 microsoft/TypeScript#42192. An object type like Record<`foo${string}`, number> is unfortunately treated very much like {}; it will not complain if you assign a type like {fooOops: "This is not a number"} to it. ( Well, at least this part is fixed for TS4.4; pattern template literals can be used in index signatures as per microsoft/TypeScript#44512 )

所有这些放在一起意味着这不在 TS 4.4 的 TypeScript 的可能性范围内。


相反,您需要某种解决方法。我正在玩弄使用 string enum 来模拟 string 的 opaque/nominal 子类型,它与键一起使用,但它并没有比我确定的解决方法更有用:a我们 假装 "###${process.env.REACT_APP_ENV}###" 等占位符字符串文字类型是 process.env.REACT_APP_ENV 的已知实际类型。只要我们只将类型称为 process.env.REACT_APP_ENV 而不是伪装的字符串文字,一切都会成功。我们甚至可能希望假装值是 "!!!PLACEHOLDER_DO_NOT_USE_THIS!!!" 之类的东西,或者任何你需要说服人们不要使用文字类型的东西。

看起来像这样:

declare const process: {
  env: {// the following type is a fiction but is the best I can do 
    REACT_APP_ENV: "###${process.env.REACT_APP_ENV}###"
  }
}

然后你的 claimKey 将是一个 const-asserted 模板字符串,以便编译器可以连接它并保持它的字符串文字性:

const claimKey = `urn:${process.env.REACT_APP_ENV}:claims/user-id` as const

一切都按预期进行,主要是:

interface MyInterface {
  email: string;
  [claimKey]: string;
}

const myInterface: MyInterface = {
  email: "user@example.org",
  [claimKey]: "123"
}

万岁!不过,这只是一种解决方法。不幸的是,该占位符值可能会显示为 IntelliSense 提示:

const booIntelliSense: MyInterface = {
  email: "",
  "urn:###${process.env.REACT_APP_ENV}###:claims/user-id": "oops" // <-- hinted by IntelliSense!
}

所以它真的不完美。好吧。

Playground link to code