从映射解析并间接调用的函数的类型推断
Type inference of function resolved from a map and called indirectly
我正在尝试使打字稿类型与一种模式一起工作,其中有一个 对象 函数和一个函数 call(name, arg)
在 object by 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
(以及 return 类型 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'
无法从打字稿中的函数类型'extract out'参数类型。
如果您愿意做一些额外的工作,您可以使用单独编码参数类型和 return 类型的数据结构为注册表定义类型。该数据结构不在运行时使用,仅用作编译器进行类型推断的指南,因此您可以对 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
我基本上同意@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
祝你好运!
我正在尝试使打字稿类型与一种模式一起工作,其中有一个 对象 函数和一个函数 call(name, arg)
在 object by 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
(以及 return 类型 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'
无法从打字稿中的函数类型'extract out'参数类型。
如果您愿意做一些额外的工作,您可以使用单独编码参数类型和 return 类型的数据结构为注册表定义类型。该数据结构不在运行时使用,仅用作编译器进行类型推断的指南,因此您可以对 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
我基本上同意@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
祝你好运!