根据包含类型化函数的对象正确键入返回类型



我正在尝试编写一个函数,该函数接收某个参数以及从该参数类型到各种返回类型的函数映射。返回类型应该是一个与函数映射具有相同键的对象,但值应该是将函数应用于参数的结果。

因此,基本上它将被如下使用:

interface Person {
name: string;
age: number;
friends: number;
}
const bunchOfPeople: Person[] = [
{ name: "Tom", age: 6 , friends: 2 },
{ name: "Dick", age: 16, friends: 12 },
{ name: "Harry", age: 26, friends: 5 },
];
const stats = applyFuncs(bunchOfPeople, {
count: people => people.length,
avgFriends: people => people.map(p => p.friends).reduce((a, b) => a + b) / people.length,
allowedToDrink: people => people.filter(p => p.age >= 21).map(p => p.name),
});
// Expected stats:
// {
//   "count": 3,
//   "avgFriends": 6.333333333333333,
//   "allowedToDrink": ["Harry"],
// } 

我的第一次尝试是使用函数的返回类型来推断输出值类型:

function applyFuncs<T, Fs extends {[K in keyof Fs]: (arg: T) => any}>(arg: T, funcs: Fs) {
var result = {} as {[K in keyof Fs]: ReturnType<Fs[K]>};
for (const key of (Object.keys(funcs) as Array<keyof Fs>)) {
result[key] = funcs[key](arg);
}
return result;
}

这按预期工作,但由于某些原因,编译器无法推断函数参数的类型,这意味着people变量的类型为any。假设通用函数签名表示Fs类型的值是从Tany的函数,我本希望people能被正确地推断为与bunchOfPeople(即Person[](相同的类型。

TS游乐场在这里。

因为TypeScript是静态和结构类型化的,所以对象可能具有超出对象类型中定义的多余属性。这使得从静态类型系统的角度来看,执行使用在运行时枚举对象键的方法(如Object.keys(obj)Object.entries(obj)等(的操作是不安全的。

如果你意识到了这一点,并接受了如果你在以这种方式使用对象之前不验证对象,可能会出现错误的可能性,那么你可以使用一些断言来获得你想要的类型:

TS游乐场

type Fn<
Params extends unknown[] = any[],
Result = any,
> = (...params: Params) => Result;
function mapResults <
Args extends unknown[],
T extends Record<PropertyKey, Fn<Args>>
>(args: Args, fnMap: T): { [K in keyof T]: ReturnType<T[K]> } {
const results = {} as { [K in keyof T]: ReturnType<T[K]> };
for (const [name, fn] of Object.entries(fnMap)) {
results[name as keyof T] = fn(...args);
}
return results;
}
interface Person {
age: number;
friends: number;
name: string;
}
const people: Person[] = [
{ name: "Tom", age: 6 , friends: 2 },
{ name: "Dick", age: 16, friends: 12 },
{ name: "Harry", age: 26, friends: 5 },
];
const stats = mapResults([people], {
count: people => people.length,
avgFriends: people => people.map(p => p.friends).reduce((a, b) => a + b) / people.length,
allowedToDrink: people => people.filter(p => p.age >= 21).map(p => p.name),
});
console.log(stats);

最新更新