Typescript 从映射解析并间接调用的函数的类型推断

Typescript 从映射解析并间接调用的函数的类型推断,typescript,type-inference,typescript-generics,Typescript,Type Inference,Typescript Generics,我正试图使typescript键入使用一种模式,其中有一个函数对象和一个函数调用(name,arg),它通过name在对象中查找函数,并使用arg调用函数 假设我有一个将名称映射到函数的对象: 接口注册表{ str2numb:(p:string)=>number num2bool:(p:number)=>布尔值 } 常量注册表:注册表={ str2numb:p=>parseInt(p,10), num2bool:p=>!!p, } 我还有一个函数call(name,p),它从注册表中解析函数,

我正试图使typescript键入使用一种模式,其中有一个函数对象和一个函数
调用(name,arg)
,它通过
name
在对象中查找函数,并使用
arg
调用函数

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

接口注册表{
str2numb:(p:string)=>number
num2bool:(p:number)=>布尔值
}
常量注册表:注册表={
str2numb:p=>parseInt(p,10),
num2bool:p=>!!p,
}
我还有一个函数
call(name,p)
,它从
注册表中解析函数
,并用
p
调用它。现在,我想键入函数,以便在提供无效参数时,它会抱怨:

constcall=(name,p)=>REGISTRY[name](p)
呼叫('str2numb',123)
//^^^希望在此处看到错误
如何从注册表的类型中解析参数
p
(以及返回类型
R
)的类型
p
?有可能吗

//这里如何解析P和R?
//解析的函数为注册表[N]
//我尝试了注册表[N],但不起作用:-(
常量调用=(名称:N,p:p):R=>REGISTRY[name](p)
我已经走了这么远,但它不起作用:

type Fn=(p:p)=>R
常数呼叫=
(名称:N,p:p):R=>
注册表[名称](p)
呼叫('str2numb',123)
//^^^此处没有错误
然而,这是可行的:

//这只是返回已解析的函数
常量call1=(名称:N)=>注册表[名称]
//从名称正确推断返回函数的类型
call1('str2numb')(123)
//^123类型的参数不能分配给string类型的参数

无法从typescript中的函数类型中“提取”参数类型

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

// 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
//用于对参数和结果类型进行编码
//如下所示:param().result()
函数param

():{result():{P:P[],r:r[]}{ 返回{ 结果(){ 返回{p:[],r:[]}//使用空数组,这样我们就没有 //提供价值 } } } 常量注册表类型={ str2numb:param().result(), num2bool:param().result() } 类型RegistryTypes=RegistryTypes的类型; //这与问题中的“接口注册表”类型相同 类型注册表={[N in keyof RegistryTypes]:(p:RegistryTypes[N]['p'][0])=>RegistryTypes[N]['r'][0]}; 常量注册表:注册表={ str2numb:p=>parseInt(p,10), num2bool:p=>!!p, } 让我们调用:(n:n,p:RegistryTypes[n]['p'][0])=>RegistryTypes[n]['r'][0]; const n=call('str2numb','2');//好的,n是一个数字 const n1=call('str2numb',2);//错误


我基本上同意@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
//编译器的类型
类型注册表架构={
str2numb:{参数:字符串,结果:number};
num2bool:{参数:数字,结果:布尔};
}
//根据RegistrySchema表示注册表
类型注册表={
[K in keyof RegistrySchema]:
(参数:RegistrySchema[K]['argument'])=>RegistrySchema[K]['result']
}
//和以前一样的注册表
常量注册表:注册表={
str2numb:p=>parseInt(p,10),
num2bool:p=>!!p,
}
//调用可以这样定义
函数调用(
k:k,
参数:RegistrySchema[K]['argument']
):RegistrySchema[K]['result']{
返回注册表[k](参数);
}
//它起作用了
const x=call('str2numb',123);//错误
const y=call('str2numb',“hello”);//y是号码
祝你好运