This question is more of the nature of best practices than why or why not something must work in a certain way.
After reading Why doesn't Object.keys return a keyof type in TypeScript? and a bunch of other documentation, I think I get why the return type of Object.keys can not be assumed to be keyof typeof myArray
.
But, assuming Object.entries
suffers from the same issue/limitation as Object.keys
, it is still confusing to me that this works:
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
I don't use any type annotations or casts and let the typescript compiler do all the work through inference. Still this returns ((str: string) => string) | undefined
, which is exactly what I want - either it found the requested function (str: string) => string
or it didn't undefined
.
Is this good practice? Is it just better to use an array of objects with key/value pairs directly, instead of an object literal in this type of cases? Or is it a compiler error?
UPDATE: More examples to better explain where I am coming from with this question.
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