将对象数组映射到打字稿中的字典,id作为键

问题描述 投票:0回答:1

给定一些类型和数据,如下所示, 我如何以一种可以自动完成键的方式暗示类型,即

dictionary.Germany

type Entry = {
  tld: string;
  name: string;
  population: number;
};

const data: Entry[] = [
  {tld: 'de', name: 'Germany', population: 83623528},
  {tld: 'at', name: 'Austria', population: 8975552},
  {tld: 'ch', name: 'Switzerland', population: 8616571}
];

let dictionary = Object.fromEntries(data.map(item => [item.name, item]));

(现在

dictionary
属于
{ [key: string]: Entry }
类型,例如
{ Germany: {tld: 'de', …}, …}
。)


需要明确的是,我的目标是:

  1. 最终结果是在两个 Entry 对象列表中都有数据......
  2. …以及带有 Entry 对象的对象映射。
  3. 我只需要写一次对象的名称,可以是
    name
    或键。
  4. 字典需要知道其在 WebStorm 等 IDE 中的键。
  5. dictionary.Germany === data[0]
typescript key
1个回答
0
投票

有许多问题阻止您的代码按原样工作。


首先,您将data的类型

注释
Entry[]
,从而告诉编译器丢弃有关初始化数组文字的任何更具体的信息。
Entry
只知道
name
string
。但是您关心 name 属性的字符串
literal types

您可能应该使用

const
断言来告诉编译器您希望它跟踪初始化程序的所有文字类型,而不是注释(如果您只关心
name
,那么这有点过分了,但它不一定会伤害任何东西)。然后,如果您想确保它与
Entry[]
匹配,您可以使用 the
satisfies
运算符
:

const data = [
  { tld: 'de', name: 'Germany', population: 83623528 },
  { tld: 'at', name: 'Austria', population: 8975552 },
  { tld: 'ch', name: 'Switzerland', population: 8616571 }
] as const satisfies Entry[];

(由于 microsoft/TypeScript#55229,上述内容仅适用于 TypeScript 5.3 或更高版本;在 TypeScript 4.9 到 5.2 中,您需要使用

readonly Entry[]
来代替。)


现在 TS 知道

"Germany"
"Austria"
"Switzerland"
,但它不知道 Object.fromEntries()
 方法
使用这些键生成值。当前的
类型定义看起来像

interface ObjectConstructor { fromEntries<T = any>(e: Iterable<readonly [PropertyKey, T]>): { [k: string]: T; }; }
意味着它的输出总是有一个

string

索引签名。正如 microsoft/TypeScript#35745 中所建议的那样,编写更具体的调用签名并非不可能,但要正确实现它很棘手,而且不值得这么复杂。

如果您想自己编写这样的签名,您可以在自己的代码库中这样做并

将其合并到中,如下所示:

// declare global { interface ObjectConstructor { fromEntries<E extends readonly [PropertyKey, any][]>( entries: E ): { [T in E[number] as T[0]]: T[1] }; } // }
迭代 

entries

 的元素并使用 
键重新映射 来生成对象类型。


在此之后你就差不多完成了。尽管如此,当您真正想要的是

dictionary

 时,
const 的结果类型仍会将 Entry
 断言垃圾的可怕 
union
 作为其属性值类型。因此,我们可以使用 
item satifies Entry as Entry
item
 安全地加宽为 
Entry
(写 
item satisfies Entry
 会检查它,但不会加宽它,写 
item as Entry
 会加宽它,但不会检查它)。

最后,这给了我们:

let dictionary = Object.fromEntries(data.map(item => [item.name, item satisfies Entry as Entry])); /* let dictionary: { Germany: Entry; Austria: Entry; Switzerland: Entry; } */
现在你或多或少已经有了你想要的行为。这值得么?我想这取决于您的用例。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.