使用typescript提取函数参数中函数参数的类型

1tuwyuhd  于 2023-06-24  发布在  TypeScript
关注(0)|答案(2)|浏览(197)

我试图为函数myFunction创建类型设置,但返回的类型不正确。

type Base = {
  [key: string]: (state: any, payload: {
    type: any
    payload?: any
  }) => any
}

const myFunction = <T extends Base>(arg: T) => {

  return {
    actions: {} as {
      [Key in keyof T]: {
        [K in Parameters<T[Key]>[1]['type']]: (arg: Parameters<T[Key]>[1] & { type: K }) => void
      }
    }
  }
}

const reducer1 = (state: boolean, payload: { type: 'foo', payload: number } | { type: 'batman' }) => state
const reducer2 = (state: string, payload: { type: 'bar' }) => state

const {actions} = myFunction({
  domain1: reducer1,
  domain2: reducer2
})

返回的actions对象应该有两个属性domain1domain2,每个域应该有相应的函数。
但是我似乎不能得到正确的函数参数:

actions.domain1.batman() // no argument should be required
actions.domain1.foo()    // argument should be number
actions.domain2.bar()    // no argument should be required

看看这个Playground。

jdgnovmf

jdgnovmf1#

这里的主要问题是(Parameters<T[Key]>[1] & { type: K })将具有typepayload属性,因此(arg: Parameters<T[Key]>[1] & { type: K }) => void将需要该类型的输入。但是您实际上希望输入只是payload属性。这意味着你只是忘记了用"payload"index转换成该类型:

declare const myFunction: <T extends Base>(arg: T) => {
  actions:
  { [Key in keyof T]: { [K in Parameters<T[Key]>[1]['type']]:
    (arg: (Parameters<T[Key]>[1] & { type: K })["payload"]) => void
    // --------------------------------------> ^^^^^^^^^^
  } }
};

这将为您提供以下理想行为:

actions.domain1.foo(3); // okay
actions.domain1.foo(); // error, requires an argument

但是你仍然会遇到没有payload的类型需要一个参数的情况:

actions.domain1.batman() // error, 
// (property) batman: (arg: unknown) => void
actions.domain2.bar() // error
// (property) bar: (arg: unknown) => void

它需要一个unknown类型的参数,因为像{type: "batman"}这样的类型没有已知的payload属性,所以实际上任何类型都可以在这样的索引中。
相反,我认为你想要的是,如果payload可能是undefined,那么你希望arg是一个可选参数。我们可以编写一个实用程序类型,如

type FuncWithPossiblyOptionalParam<T> =
  undefined extends T ? (arg?: T) => void : (arg: T) => void;

其使用conditional type在两种类型的功能之间切换。然后我们使用它:

declare const myFunction: <T extends Base>(arg: T) => {
  actions:
  { [Key in keyof T]: { [K in Parameters<T[Key]>[1]['type']]:
    FuncWithPossiblyOptionalParam<
      (Parameters<T[Key]>[1] & { type: K })["payload"]
    >
  } }
};

现在你得到了你想要的行为:

actions.domain1.foo(3); // okay
actions.domain1.foo(); // error, requires an argument
actions.domain1.batman() // okay
actions.domain2.bar() // okay

这基本上是对所问问题的回答。请注意,我们可以简化mapping over Parameters<T[Key]>[1]["type"]的技术,然后在另一边提取相应的联合成员。
您可以通过使用键重Map来迭代Map类型中的任意联合。因此,我们可以迭代Parameters<T[Key]>[1],而不是Parameters<T[Key]>[1]["type"],然后直接提取type作为键,payload作为值:

declare const myFunction: <T extends Base>(arg: T) => {
  actions: { [Key in keyof T]:
    { [P in Parameters<T[Key]>[1] as P["type"]]:
      FuncWithPossiblyOptionalParam<P["payload"]>
    };
  };
}

这不会改变结果类型:

actions.domain1.foo(3); // okay
actions.domain1.foo(); // error, requires an argument
actions.domain1.batman() // okay
actions.domain2.bar() // okay

但它更简单。
Playground链接到代码

eyh26e7m

eyh26e7m2#

接受的答案是伟大的,但我遇到了一个问题,如果所有的有效载荷是mandetory。
看看这个Playground。
我目前正在寻找解决方案。
更新:似乎从Base类型中删除?解决了它...?
更新的Playground在这里。

相关问题