如何使用具有 const 断言(作为 const)的对象作为另一种类型键的唯一符号

How to use an object with const assertion (as const) as an unique symbol for another type key

我有一个路线列表:

const N: any = "N"; // get const from non typed lib for example or typed as "string"
const PREFIX = `Route_${N}` as const;
const Route = {
  Route1: `${PREFIX}_1`,
  Route2: `${PREFIX}_2`
} as const; // I use object litteral because enum doesn't allow computed value

我想为每条路线输入参数:

type ParamsList = {
  [Route.Route1]: undefined;
  [Route.Route2]: { value: string };
};

但不允许出现此 TypeScript 错误:

A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.ts(1170)

如何将我的路线名称用作我的参数列表类型的唯一符号键? const 断言 (as const) 不应使对象键唯一?

感谢您的帮助。

新答案

让我们解压错误信息:

A computed property name in a type literal must refer to an expression whose type is a literal type is a literal type or a 'unique symbol' type.ts(1170)

“计算的 属性 名称”指的是最终 ParamsList 类型中的属性 -(例如 [Route.Route1]: undefined; 中的 Route.Route1)。

所以打字稿说 Route.Route1 必须是最终计算为“文字类型”的表达式(例如,像 "Route1_N_1" 这样的特定字符串,或者像 42 这样的特定数字) , 或 Symbol (这里不重要,但如果你好奇,检查 the Symbol docs)

Route.Route1 当前的计算结果如下:

`Route_${any}_1`

这被称为“模板文字类型”(参见 docs)- 与文字字符串(例如 "Route_N_1" 不同 ). TS 也需要这个是有道理的——它使编译器处于这样一种情况,即可以为 ParamsList 上的相同 属性 定义两种(或更多)不同的类型,如果有多个模板文字类型可以以重叠的方式扩展。

所以如果你想解决这个问题,你需要以某种方式使 typescript 能够将 Route.Route1 计算为特定字符串,而不是通用字符串。

The docs say 模板文字类型“能够通过联合扩展成许多字符串”,这正是您想要的。为了触发这个,你必须确保所有模板位置(例如 Route_${N} 中的 ${N} 都得到特定的文字字符串类型(例如 "foo""bar",而不是东西像 anystring).

这是一种方法:

// const N: any = "N"; // original type "any" <--this won't work
const N = "N" as const; // new type "N" <-- this will

const PREFIX = `Route_${N}` as const; // new type: "Route_N"; original type: "Route_${any}"
const Route = {
  Route1: `${PREFIX}_1`, // new type: "Route_N_1"; original type: "Route_${any}_1"
  Route2: `${PREFIX}_2` // new type: "Route_N_2"; original type: "Route_${any}_2"
} as const;

type ParamsList = {
  [Route.Route1]: undefined;
  [Route.Route2]: { value: string };
};

参见 this playground example

更新:如果您正在构建的库不提供字符串文字类型,如何向其提供字符串文字类型

在上面的示例中,我们手动创建了 N 变量,因此很容易提供带有 as const 断言的字符串文字类型。

然而,如果 N 实际上是从另一个库导入的东西,它具有 any 类型,我们将需要以某种方式自己提供这种类型。正如您在评论中指出的那样,一种方法是在末尾添加一个 as "MyString" 断言。

另一种方法是使用 module augmentation combined with declaration merging。在您在评论中提供的具体示例中,您是这样做的:

import { StyleSheet, Text, View, NativeModules } from "react-native";
const { NativeNaviagtion } = NativeModules;
// NativeNavigation.Page1 <-- this has type "any"

您可以“扩充”"react-native”模块提供的 NativeModules 类型,以便 NativeModules.NativeNavigation.Page1 具有文字字符串类型 "Page1",方法是:

declare module "react-native" {
  interface NativeModulesStatic {
    NativeNaviagtion: { Page1: "Page1" };
  }
}

有关不同上下文中的类似问题,请参阅 this codesandbox example and

旧答案

您的原始示例(没有 const N: any = "N" 的附加间接层)将在 TypeScript 4.1+ 中运行,但在 TypeScript 4.0- 中不起作用。那是因为TS在4.1中加入了Template Literal Type特性。

在 4.1 之前,Route const 将具有这种类型:

const Route: {
    readonly Route1: string;
    readonly Route2: string;
}

在 4.1 之后,Route const 具有这种类型:

const Route: {
    readonly Route1: "Route_1";
    readonly Route2: "Route_2";
}

所以对于 4.0- 版本的打字稿,这...

type ParamsList = {
  [Route.Route1]: undefined;
  [Route.Route2]: { value: string };
};

...将被解释为...

type ParamsList = {
  [string]: undefined;
  [string]: { value: string };
};

...这是不明确的,并抛出该错误。在 4.1+ 中,它被正确地解释为:

type ParamsList = {
    Route_1: undefined;
    Route_2: {
        value: string;
    };
}