通过 Object.entry vs Object.keys 从对象文字中获取属性



这个问题更多的是最佳实践的本质,而不是为什么或为什么不以某种方式工作。

在阅读了为什么 Object.keys 不返回 TypeScript 中的 keyof 类型?以及一堆其他文档后,我想我明白为什么不能假设 Object.keys 的返回类型是keyof typeof myArray的。

但是,假设Object.entries遇到与Object.keys相同的问题/限制,我仍然感到困惑:

const map = {
a: (str: string) => str,
b: (str: string) => str,
c: (str: string) => str,
};
function getFn(fnName: string) {
const fn = Object.entries(map).find((e) => e[0] === fnName)?.[1];
// Type:
// const fn: ((str: string) => string) | undefined
return fn;
}
// Type:
// function getFn(fnName: string): ((str: string) => string) | undefined

我不使用任何类型注释或强制转换,让打字稿编译器通过推理完成所有工作。这仍然返回((str: string) => string) | undefined,这正是我想要的 - 要么它(str: string) => string找到请求的函数,要么它没有undefined.

这是好的做法吗?在这种情况下,直接使用具有键/值对的对象数组而不是对象文字更好吗?还是编译器错误?

更新:更多示例可以更好地解释我对这个问题的看法。

const map = {
a: (str: string) => str,
b: (str: string) => str,
c: (str: string) => str,
};
// With Object.entries and Array.find (WORKS)
function getFn(fnName: string) {
const fn = Object.entries(map).find((e) => e[0] === fnName)?.[1];
// Type:
// const fn: ((str: string) => string) | undefined
return fn;
}
// Type:
// function getFn(fnName: string): ((str: string) => string) | undefined
// With assertions (WORKS BUT CLUNKY)
function getFn2(fnName: string) {
if (fnName === 'a' || fnName === 'b' || fnName === 'c') {
return map[fnName];
}
}
// Type:
// function getFn2(fnName: string): ((str: string) => string) | undefined
// With assertions - using Object.keys (DOES NOT WORK)
function getFn3(fnName: string) {
if (Object.keys(map).includes(fnName)) {
return map[fnName]; // ts(7053)
}
}
// Type:
// function getFn3(fnName: string): any
// With assertions - using Object.entries (ALSO DOES NOT WORK)
function getFn4(fnName: string) {
if (
Object.entries(map)
.map((e) => e[0])
.includes(fnName)
) {
return map[fnName]; // ts(7053)
}
}
// Type:
// function getFn4(fnName: string): any
// With Reflect (WORKS BUT RETURNS 'ANY' OR REQUIRES MANUAL TYPING)
function getFn5(fnName: string) {
const fn: undefined | ((str: string) => string) = Reflect.get(map, fnName);
return fn;
}
// Type:
// function getFn5(fnName: string): ((str: string) => string) | undefined

好的,基本上有两种方法可以做到这一点。如果没有一些手动输入,它们都不会让你过得去:TS 编译器已经是一项非常了不起的工程壮举,因为它在推理方面做得和推理一样好,因为你不仅从动态语言开始,而且从超动态语言开始。

解决方案1:简单转换,推荐

function getFn(name: string) {
if (name in map) { // for more safety, ['a', 'b', 'c'].includes(name)
return map[name as keyof typeof map];
}
return;
}

解决方案 2:用户定义的类型防护。

// More type-safe in more complex use-cases, probably overkill here.
function inMap(name: string | keyof typeof map): name is keyof typeof map {
// As above, depending on how you want to play this
// for more safety use ['a', 'b', 'c'].includes(name)
return name in map; 
}
function getFn2(name: string) {
if (inMap(name)) {
return map[name];
}
return;
}

操场

有趣的是,解决这个问题的一种简单方法是稍微放松地图对象的类型:

操场

const map: Record<string, ((str: string) => string) | undefined> = {
a: (str: string) => str,
b: (str: string) => str,
c: (str: string) => str,
};
const key: string = 'stuff'
const fn = map[key]
// Type -> const fn: ((str: string) => string) | undefined

或者使用实际的Map对象:

操场

const map = new Map<string, (str: string) => string>()
map.set('a', (str: string) => str)
map.set('b', (str: string) => str)
map.set('c', (str: string) => str)
const key: string = 'stuff'
const fn = map.get(key)
// Type -> const fn: ((str: string) => string) | undefined

没有迭代或数组,也没有类型转换。真棒公司

最新更新