元素在转换中隐式具有任何类型

时间:2019-10-10 05:58:57

标签: typescript

我不知道如何正确键入此函数:

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的键的子集。

2 个答案:

答案 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 }]

Playground