1

我正在尝试使打字稿打字与一种模式一起工作,其中有一个函数对象和一个函数,该函数在对象call(name, arg)查找函数并使用.namearg

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

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(以及返回类型RRegistry.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'
4

2 回答 2

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

祝你好运!

于 2017-12-14T18:49:48.783 回答
0

无法从打字稿中的函数类型中“提取”参数类型。

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