如何修复通用TypeScript函数以返回有效的推断类型?

时间:2019-10-24 07:54:41

标签: typescript typescript-generics

我有下面的TypeScript代码(link to playground):

type MyCallback<T> = (s: string, payload: T) => void;

interface IActions {
  do1: MyCallback<number>;
  do2: MyCallback<string>;
  [key: string]: (s: string, payload: any) => void;
}

function convert<T extends { [key: string]: (s: string, payload: any) => void }>(callbackMap: T) {

  const result: { [key: string]: <U>(payload: U) => void } = {};

  Object.keys(callbackMap).forEach(key => {
    if (typeof callbackMap[key] === 'function') {
      result[key] = callbackMap[key].bind(null, "data");
    }
  })

  return result;
}

const maps = convert<IActions>({
  do1: (s: string, payload: number) => {
    //
  },
  do2(s: string, payload: string) {
    //
  }
});

maps.do1(1); // valid
maps.smth("1"); // should be type-check error, but TS thinks it's valid

我想做的是创建一个函数,该函数通过接口接受对象。该函数将所有方法从该对象转换为新对象,其中所有方法都有一个固定参数(通过bind方法)。换句话说,我想转换这个界面

interface IActions {
  do1: (state: string, payload: number);
  do2: (state: string, payload: string);
  .....
}

interface IActions {
  do1: (payload: number);
  do2: (payload: string);
  ....
}

我想使其通用,因此它将基于通用参数转换任何接口。

当前方法的问题是我没有maps对象的任何智能感知和类型检查。

是否可以通过这样的方式修改我的convert函数,即传入接口会自动推断出返回类型?换句话说,我具有完整类型检查和返回值的智能感知(在我的情况下为maps)。

1 个答案:

答案 0 :(得分:1)

maps.smth有效的事实归因于结果上的显式索引签名。这里需要的是一种映射类型,用于将IActions的属性映射到包含修改后的方法的新类型。要创建新的方法签名,我们可以使用条件类型来提取其余参数(跳过第一个参数)

type MyCallback<T> = (s: string, payload: T) => void;

interface IActions {
  do1: MyCallback<number>;
  do2: MyCallback<string>;
}

function convert<T extends Record<keyof T, (s: string, payload: any) => void>>(callbackMap: T) {

  const result: Record<string, (...a: any[]) => any> = {}

  Object.keys(callbackMap).forEach(key => {
    if (typeof callbackMap[key as keyof T] === 'function') {
      result[key] = callbackMap[key as keyof T].bind(null, "data");
    }
  })

  return result as {
    [P in keyof T]: T[P] extends (s: string, ...p: infer P) => infer R ? (...p: P) => R : never;
  };
}

const maps = convert<IActions>({
  do1: (s: string, payload: number) => {
    //
  },
  do2(s: string, payload: string) {
    //
  }
});

maps.do1(1); // valid
maps.do1("1"); //err
maps.smth("1"); // should be type-check error, but TS thinks it's valid

Play