TypeScript:从字符串数组定义联合类型

96

我可能不是第一个遇到这个问题的人,但是我的搜索还没有找到任何有用的线索。非常感谢一些专家 TypeScript 建议。

假设我有一个数组:

const fruits = ["Apple", "Orange", "Pear"];

我想定义一个对象,将每种水果映射到一些有趣的事实:

interface Facts {
    color: string,
    typicalWeight: number
}

const fruitFacts: { [key: members of fruits]: Facts } = {
    "Apple": { color: "green", typicalWeight: 150 }
    //
}

我该如何处理 [key: members of fruits] 部分?

额外奖励问题:我该如何确保我的 fruitFacts 对象用完了上面例子中数组导出的所有键,以便为苹果、橙子和梨子指定事实。


1
你是否在编译时知道确切的字符串?如果不知道,就无法定义这样一种类型。 - Mikhail Burshteyn
假设我这样做。我能避免重复吗?即避免执行 type FruitName = "Apple" | "Orange"; const fruitNames : FruitName[] = ["Apple", "Orange"]; 这段代码吗? - Arash Motamedi
https://dev59.com/FVcO5IYBdhLWcg3wxEcP#45257357 - tokland
2个回答

176
TypeScript 3.4 添加了 const 断言,允许将其编写为:
const fruits = ["Apple", "Orange", "Pear"] as const;
type Fruit = typeof fruits[number]; // "Apple" | "Orange" | "Pear"

使用 as const,TypeScript 推断出上面的 fruits 的类型为 readonly["Apple", "Orange", "Pear"]。以前,它会将其推断为 string[],从而防止 typeof fruits[number] 产生所需的联合类型。

1
为什么这个不起作用?:const fruitTypes = ["Apple", "Orange", "Pear"]; const fruits = fruitTypes as const; - techguy2000
4
我认为原因是你可能拥有以下代码:const fruitTypes = ["Apple", "Orange", "Pear"]; fruitTypes.push("Kiwi"); const fruits = fruitTypes as const;在这种情况下,TS无法可靠地知道类型现在应该是 ["Apple", "Orange", "Pear", "Kiwi"];,因此在最初定义后允许将其标记为 const 是一种不安全的模式。 - Ben Regenspan
2
当我冻结数组时,它仍然无法工作:const fruitTypes = Object.freeze(["Apple", "Orange", "Pear"]); 我真的希望这个的某个变体能够工作... - techguy2000
2
@techguy2000,这可能值得在TS问题跟踪器中提出作为一个功能建议,似乎将此案例类型化为readonly["Apple", "Orange", "Pear"]而不是readonly string[]是合理的。 - Ben Regenspan
2
@Batman 写下 typeof fruits[number] 会告诉 TypeScript 我们关心的是存储在 fruits 数组内的值的类型。因为它是一个数组,这些值是由 number 索引的。用简单的语言来说,就像我们在询问 TypeScript:“对于从 fruits 请求的任何给定整数索引,返回的值可能有哪些类型?” - Ben Regenspan
显示剩余7条评论

28

这是可以实现的,但首先需要一个额外的函数来帮助推断数组元素的字符串字面类型。默认情况下,即使它是常量,Typescript也会为数组推断 string[] 类型。在我们有了字符串字面类型的数组后,我们只需要使用类型查询来获取所需的类型。

function stringLiteralArray<T extends string>(a: T[]) {
    return a;
}

const fruits = stringLiteralArray(["Apple", "Orange", "Pear"]);
type Fruits = typeof fruits[number]

从3.4版本开始,您可以使用const类型断言来替代stringLiteralArray函数:

const fruits = ["Apple", "Orange", "Pear"] as const;
type Fruits = typeof fruits[number]

2
这个特定的 typeof fruits[number] 调用正是我所需要的 - 它将它变成了一个字符串联合类型,而不是只读字符串数组。 - radicand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接