我不知道如何正确键入此函数:
function makeTransform<TObj extends Record<string,any>, K extends keyof TObj>(transformer: Record<K, (x:TObj[K])=>any>): (o:TObj)=>any {
return (obj: TObj) => {
const out: any = {...obj}
for (const k of Object.keys(transformer)) {
out[k] = transformer[k](obj[k])
}
return out;
}
}
用法:
const trans = makeTransform({a:x => x*2});
const arr = [{a:1,b:2},{a:3,b:4}];
console.log(arr.map(trans)) // [ { a: 2, b: 2 }, { a: 6, b: 4 } ]
错误:
我知道它是从string
那里提取Record<string,any>
的,但是我不确定我还能在那放什么?它应该仅具有字符串键,但是transformer
应该具有TObj
的键的子集。
答案 0 :(得分:1)
功劳归用户k48(answer:https://stackoverflow.com/a/55721107/5536304)
以下解决方案会编译:
function makeTransform<TObj extends Record<string, any>, K extends keyof TObj>(transformer: Record<K, (x: TObj[K]) => any>): (o: TObj) => any {
return (obj: TObj) => {
const out: any = { ...obj }
for (const k in transformer) {
out[k] = transformer[k](obj[k]);
}
return out;
}
}
const trans = makeTransform({ a: x => x * 2 });
const arr = [{a:1,b:2},{a:3,b:4}];
console.log(arr.map(trans)) // [ { a: 2, b: 2 }, { a: 6, b: 4 } ]
答案 1 :(得分:1)
transformer
期望类型为k
(您的通用类型参数)的密钥K
,但是Object.keys
的签名为keys(o: object): string[]
,仅返回未变窄的字符串,并且因此会导致编译错误。
您可以在this answer中找到可能的解决方法,这里最好使用类型断言。
另一个值得注意的问题是无法为外部函数中的TObj
参数正确推断类型参数transformer
(例如,当trying to declare x
as number时会出现问题)。仅当您以渐进方式用实际的obj: TObj
调用内部函数时,才能稍后解析此类型。这也许也是原因,为什么要使用这么多的any
类型-省略any
始终是一个好主意,以强制使用更强的类型。
相反,基本思路可能是,我们将makeTransform
函数的类型完全基于transformer
回调。 TObj
现在是内部函数的类型参数,必须满足外部transformer
的类型约束。此外,我们摆脱了所有any
类型和不必要的类型断言。
function makeTransform<
T extends Record<string, (arg: any) => any>
>(transformer: T) {
return <TObj extends { [P in keyof T]: Parameters<T[P]>[0] }>(obj: TObj) => {
const objClone: Omit<TObj, keyof T> = { ...obj };
// we have to cast here, only endresult will fit the type
const mapped = {} as { [P in keyof T]: ReturnType<T[P]> };
for (const k of Object.keys(transformer)) {
// just one way of working around Object.keys types
const kC = k as keyof T;
mapped[kC] = transformer[kC](obj[kC]);
}
return { ...objClone, ...mapped };
};
}
测试:
const trans = makeTransform({
a: (x: number) => String(x * 2),
b: (x: string) => parseInt(x)
});
const res0 = [{ a: 1, b: "11" }, { a: 2, b: "22" }].map(trans);
const res1 = [{ a: 1, b: "11", c: new Date() }, { a: 2, b: "22", c: {} }].map(trans);
const res2 = [{ a: 1 }, { a: 2, b: "22" }].map(trans); // error: b not found (OK)
const res3 = [{ a: 1, b: "11" }, { b: "22" }].map(trans); // error: a not found (OK)
const res4 = [{ a: "1", b: "11" }, { a: 2, b: "22" }].map(trans); // error: 'a' incompatible (OK)
const res5 = [{ a: 1, b: "11" }, { a: 2, b: 22 }].map(trans); // error: 'b' incompatible (OK)
console.log(res0); // [{ a: "2", b: 11 }, { a:"4", b: 22 }]