给定一些类型和数据,如下所示, 我如何以一种可以自动完成键的方式暗示类型,即
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', …}, …}
。)
需要明确的是,我的目标是:
name
或键。dictionary.Germany === data[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
将其合并到中,如下所示:
// 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;
} */
现在你或多或少已经有了你想要的行为。这值得么?我想这取决于您的用例。