键入从地图解析并间接调用的函数推断

时间:2017-12-14 17:56:51

标签: typescript type-inference typescript-generics

我试图让打字稿打字工作的模式中有一个对象的函数和一个函数call(name, arg),它在对象中查找函数 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

如何根据P的类型解析参数p的类型R(以及返回类型Registry.str2numb)?它甚至可能吗?

// 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'

2 个答案:

答案 0 :(得分: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
祝你好运!

答案 1 :(得分:0)

没有办法提取出来&#39; typecript中函数类型的参数类型。

如果您愿意做一些额外的工作,可以使用分别编码参数类型和返回类型的数据结构为注册表定义类型。该数据结构在运行时未使用,但仅作为编译器进行类型推断的指南,因此您可以对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