如何使用TypeScript动态重新映射具有功能的对象?

时间:2018-12-15 23:45:35

标签: javascript typescript

我是TypeScript的新手,并且碰到了一些东西。

我有一个普通的函数对象,这些函数在某种状态下运行,如下所示:

const queries = {
  getRoom(state: State, roomID: string) {
    return state.rooms[roomID]
  },
  isPlayerInRoom(state: State, roomID: string) {
    return state.player.roomID === roomID
  }
}

我想转换此对象,以便可以在没有第一个参数的情况下调用每个函数。

在常规JS中,我可能只是做类似的事情:

function wrap(object, store) {
  const newObject = {}
  for (const name in object) {
    const func = object[name]
    newObject[name] = (...args) => func(store.getState(), ...args)
  }
  return newObject
}

const newQueries = wrap(queries, store)
newQueries.getRoom(5)

但是使用TypeScript时,类型不会保留。

如何确保我的wrap函数具有正确的返回类型?

我确定我可以在这里采用其他方法,但是我宁愿学习如何精确地执行此操作,因为将来可能会派上用场。

谢谢!

1 个答案:

答案 0 :(得分:2)

我认为编译器不够聪明,无法遵循该函数的流程并推断出您想要它们的类型。根据TypeScript标准库,Object.entries(obj)个元组中的[string, T] returns an array个,其中Tobj所有属性类型的并集。为了甚至开始希望编译器自动为您完成此操作,您需要Object.entries(obj)返回可以编写的类型为[K0, V0] | [K1, V1] | ...的相关键值类型的数组,但这是仅仅为一个用例尝试augment键入Object.entries()可能不值得。

相反,您可以做的是自己代表所需的类型操作,这将是mappedconditional类型的组合。然后,创建一个generic函数,该函数的调用签名返回此类型,并且其实现使用所需的type assertions数量,以使编译器对无法验证的类型感到满意...或等效地,用所需的签名执行单个overload,而实现签名则不够宽松,以使编译器不会抱怨。像这样:

// pull the first argument off a function
type StripFirstArgument<F extends (...args: any[]) => any> =
  F extends (first: any, ...rest: infer A) => infer R ? (...args: A) => R : never;

// take an argument of type A, and an object of type T
// where T's properties are all functions whose first argument is type A
// and return a new object of partially applied functions  
function partiallyApplyObject<A, T extends {
  [K in keyof T]: (a: A, ...rest: any[]) => any
}>(
  a: A,
  t: T
): { [K in keyof T]: StripFirstArgument<T[K]> };

// lax implementation signature
function partiallyApplyObject(a: any, t: { [k: string]: Function }) {
  const ret: { [k: string]: Function } = {};
  for (let [k, f] of Object.entries(t)) {
    ret[k] = (...args: any[]) => t[k](a, ...args);
  }
  return ret;
}

让我们看看它是否有效:

const newQueries = partiallyApplyObject(store.getState(), queries);
newQueries.getRoom(5); // error, 5 isn't a string
newQueries.isPlayerInRoom("five"); // boolean

对我很好。也许这可能是您想要的更复杂,但是希望它是有道理的,无论如何您都可以体会到希望编译器自动执行的任务范围。

好的,希望能有所帮助。祝你好运!

相关问题