在 TypeScript 中使用符号作为对象键类型

Posted

技术标签:

【中文标题】在 TypeScript 中使用符号作为对象键类型【英文标题】:Using symbol as object-key type in TypeScript 【发布时间】:2020-03-25 20:19:50 【问题描述】:

我正在尝试使用 symbol 定义一个对象作为键类型,因为 MDN 说:

符号值可以用作对象属性的标识符 [...]

但使用它作为键属性的类型:

type obj = 
    [key: symbol | string]: string

导致以下错误:

TS1023:索引签名参数类型必须是“字符串”或“数字”。

即使它可以用作索引类型。 我正在使用最新的打字稿版本(v3.7.2),我发现的相关问题:

Typescript: destructuring an object with symbols as keys(他正在使用 Symbol 的实际实例,我想要 type symbol) TypeScript: An index signature parameter must be a 'string' or 'number' when trying to use string | number ES6: destructuring an object with symbols as keys(这不可能是一个解决方案 - 使用实际实例作为类型似乎有点错误,因为每个 Symbol 实例都是唯一的......)

我还查看了typescript symbol docs,但它们只显示了它如何用作值,而不是类型。

例子:

const obj =  as 
    [key: number | symbol]: string // Won't work
;

const sym = Symbol('My symbol');
obj[sym] = 'Hi';

Issue on Microsoft/TypeScript

Open feature request

【问题讨论】:

我认为 TypeScript 只支持其对象类型声明中的特定符号。你真的想要any symbol吗?也许展示一个你想如何使用type obj 的例子 - 我怀疑所有符号键属性都是strings。 @Bergi 我添加了一个示例,也许我已经监督了一些事情,但我找不到让 ts 接受符号的方法(不使用 any 这是不好的做法)。 见github.com/Microsoft/TypeScript/issues/24587 不确定是否正确,但您是否尝试使用 Map<Symbol,String>,因为我们有 Map ,如果这可以达到您想要达到的目的 对我来说同样的问题,我想烦人的部分是“TS 是 JS 的超集”的虚假广告——嗯,不完全是。这是一个完美的例子。 【参考方案1】:

不幸的是,目前这在 TypeScript 中是不可能的。如果您必须与一些期望这样做的 API 进行互操作,或者真的想要使用符号作为键,则可以使用这个尴尬的版本:

// Ensure we can not pass regular map to our custom functions
type SymbolMapTag =  readonly symbol: unique symbol 

type SymbolMap = SymbolMapTag & 
    [Key in string | number | symbol]: string;


function set_symbol<T extends SymbolMap, TSym extends symbol>
(target: T, sym: TSym, value: T[TSym]) 
    target[sym] = value;


function get_symbol<T extends SymbolMap, TSym extends symbol>
(target: T, sym: TSym): T[TSym] 
    return target[sym];


const symbol_map =  as SymbolMap;

const sym = Symbol('My symbol');
set_symbol(symbol_map, sym, "hi");
get_symbol(symbol_map, sym); // string


type NonSymbolMap = 
    [Key in string | number]: string;


const non_symbol_map =  as NonSymbolMap;
set_symbol(non_symbol_map, sym, "hi"); // error
get_symbol(non_symbol_map, sym); // error

【讨论】:

【参考方案2】:

TypeScript 4.4 允许symbols 进入index signatures:

type SymbolIndex = 
    [key: symbol | string]: string // works


const sym = Symbol("descr");
const t1: SymbolIndex = 
    "foo": "bar",
    [Symbol.iterator]: "qux",
    sym: "sym"
;

// all result in string
t1.foo 
t1.sym 
t1[Symbol.iterator]
t1["oh"]

Playground

对于旧版本,SymbolIndex 将触发 error:

索引签名参数类型必须是“字符串”或“数字”。(1023)

另类

如果您只想要一个带有符号且没有索引签名的对象类型,那么您今天已经可以做到了:

const sym = Symbol() // note const (no let) 
type O = 
    foo: string
    [Symbol.iterator]: string
    [sym]: number


let o: O =  [sym] : 3, [Symbol.iterator]: "bar", foo: "qux"

let  [sym]: symVal  = o

Playground

【讨论】:

如果我们有两个Symbol(),那么我们如何将[Symbol.iterator] 用于两个不同的符号 @SubratoPatnaik 老实说,我不明白你的问题。 Symbol.iterator 是一个众所周知的符号,可以为每个对象添加一次作为属性键以提供a default iterator,可与for...of 一起使用。如果您指的是类型空间还是值空间,也不清楚。 我可以在操场上看到代码,它运行没有任何问题。但是,在我使用 TypeScript 4.3 将它们复制到我的 vscode 之后,它仍然告诉我 An index signature parameter type must be either 'string' or 'number'.ts(1023) @Huan 该功能已延迟到即将推出的 TS 4.4,更新答案 谢谢,@ford04!我已经更新到 TypeScript 4.4-dev,现在一切正常。

以上是关于在 TypeScript 中使用符号作为对象键类型的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript:将对象条目映射到类型

如何将联合类型指定为对象键 Typescript

Typescript:如何声明一种值等于对象键的数组类型?

TypeScript 中每个键的泛型映射对象类型

在TypeScript中如何防止属性被添加到一个空对象中?

JS超集对TypeScript的Map对象以及联合类型的深入实战