【问题标题】:Type inference of function resolved from a map and called indirectly从映射解析并间接调用的函数的类型推断
【发布时间】:2018-05-28 21:39:32
【问题描述】:

我正在尝试使 typescript 打字与一个模式一起工作,其中有一个 object 函数和一个函数 call(name, arg),它在 object 中查找函数通过name 并使用arg 调用它。

假设我有一个将名称映射到函数的对象:

interface Registry {
  str2numb: (p: string) => number
  num2bool: (p: number) => boolean
}

const REGISTRY: Registry = {
  str2numb: p => parseInt(p, 10),
  num2bool: p => !!p,
}

我还有一个函数call(name, p),它从REGISTRY 解析函数并用p 调用它。现在,我想输入这样的函数,如果提供了无效的参数,它会抱怨:

const call = (name, p) => REGISTRY[name](p)

call('str2numb', 123)
//               ^^^ Would like to see an error here

如何从Registry.str2numb 的类型中解析参数p 的类型P(以及返回类型R)?有没有可能?

// How can I resolve P and R here?
// The resolved function is Registry[N]
// I have tried Registry[N]<P, R> but that doesn't work :-(
const call = <N extends keyof Registry>(name: N, p: P): R => REGISTRY[name](p)

我已经做到了这一点,但它不起作用:

type Fn<P, R> = (p: P) => R

const call =
  <N extends keyof Funcs, F extends Funcs[N] & Fn<P, R>, P, R>
    (name: N, p: P): R =>
      REGISTRY[name](p)

call('str2numb', 123)
//               ^^^ No error here

但是这可行:

// This just returns the resolved function
const call1 = <N extends keyof Funcs>(name: N) => REGISTRY[name]

// The type of the returned function is correctly inferred from the name
call1('str2numb')(123)
//                ^^^ Argument of type '123' is not assignable to parameter of type 'string'

【问题讨论】:

    标签: typescript type-inference typescript-generics


    【解决方案1】:

    我基本上同意@artem,为了完整性,我发布了这个相似但不相同的解决方案:

    // type for the compiler
    type RegistrySchema = {
      str2numb: { argument: string, result: number };
      num2bool: { argument: number, result: boolean };
    }
    
    // represent Registry in terms of RegistrySchema
    type Registry = {
      [K in keyof RegistrySchema]:
        (argument: RegistrySchema[K]['argument']) => RegistrySchema[K]['result'] 
    }
    
    // same REGISTRY as before
    const REGISTRY: Registry = {
      str2numb: p => parseInt(p, 10),
      num2bool: p => !!p,
    }
    
    // call can be defined thusly
    function call<K extends keyof RegistrySchema>(
      k: K,
      argument: RegistrySchema[K]['argument']
    ): RegistrySchema[K]['result'] {
      return REGISTRY[k](argument);
    }
    
    // it works
    const x = call('str2numb', 123); // error
    const y = call('str2numb', "hello"); // y is number
    

    祝你好运!

    【讨论】:

      【解决方案2】:

      没有办法从 typescript 中的函数类型中“提取”参数类型。

      如果您愿意做一些额外的工作,您可以使用分别编码参数类型和返回类型的数据结构为您的注册表定义类型。该数据结构不在运行时使用,而仅作为编译器进行类型推断的指南,因此您可以对 call 进行类型检查:

      // used to encode parameter and result type
      // like this: param<string>().result<number>()
      function param<P>(): { result<R>(): {p: P[], r: R[]}} {
          return {
              result<R>() {
                  return {p: [], r: []} // use empty arrays so we don't have 
                                        // to provide values
              }
          }
      }
      
      const registryTypes = {
          str2numb: param<string>().result<number>(),
          num2bool: param<number>().result<boolean>()
      }
      type RegistryTypes = typeof registryTypes;
      
      // this has the same type as `interface Registry` in the question
      type Registry = {[N in keyof RegistryTypes]: (p: RegistryTypes[N]['p'][0]) => RegistryTypes[N]['r'][0]};
      
      
      const REGISTRY: Registry = {
        str2numb: p => parseInt(p, 10),
        num2bool: p => !!p,
      }
      
      let call: <N extends keyof RegistryTypes>(n: N, p: RegistryTypes[N]['p'][0]) => RegistryTypes[N]['r'][0];
      
      const n = call('str2numb', '2'); // ok, n is a number
      const n1 = call('str2numb', 2); // error
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-09-11
        • 2023-02-26
        • 1970-01-01
        • 1970-01-01
        • 2016-07-12
        • 2013-03-03
        • 2018-03-02
        相关资源
        最近更新 更多