繁体   English   中英

通用包装器 function 通过给定键调用 object 的方法

[英]Generic wrapper function that calls a method of an object by a given key

我想编写一个名为foo的通用包装器 function,它采用 object o和一个键k加上一些args ,并以类型安全的方式调用o[k](...args)并推断返回类型。

这是一个例子:

const obj = {
   a: 1,
   b: true,
   c: (name: string) => `Hello, ${name}.`,
   d: (name: string) => `Goodbye, ${name}.`,
};

const result = foo(obj, 'c', 'Peter');
// typeof result === 'string'
console.log(result); // Hello, Peter.

这是我到目前为止所做的:

function foo<
  Args extends any[],
  Return,  
  Target,
  Key extends keyof {
    [K in keyof Target as Target[K] extends ((...args: Args) => any) ? K : never]: Target[K]
  }
>(target: Target, key: Key, ...args: Args): Return
{
  // typeof method === 'Target[Key]'
  const method = target[key];
  
  // This expression is not callable. Type 'unknown' has no call signatures.ts(2349)
  return method(...args);
}

// Argument of type 'string' is not assignable to parameter of type 'never'.ts(2345)
const result = foo(obj, 'c');

是否有可能在 TypeScript 中表达这样一个包装器 function 如果是这样,那么你将如何处理 go 呢?


编辑:

我接受了 Titian Cernicova-Dragomir 的回答并对其进行了调整以给我正确的返回类型:

type FunctionKeys<Target> = keyof {
  [K in keyof Target as Target[K] extends ((...args: any) => any) ? K : never]: Target[K]
}

function foo<
Target extends Record<Key, (...args: any) => any>,
Key extends FunctionKeys<Target>,
>(...[target, key, ...args]: [Target, Key, ...Parameters<Target[Key]>]) {
  const method = target[key];
  return method(...args) as ReturnType<Target[Key]>;
}

我也可以调整 Tobias S。 以同样的方式回答,但我发现使用元组会产生更好的编译器错误。 这是一个例子:

const obj = {
  greet: (name: string) => `Hello, ${name}.`,
};

// Expected 3 arguments, but got 2.ts(2554)
fooTitian(obj, 'greet');

// Argument of type '{ greet: (name: string) => string; }' is not assignable // to parameter of type 'Record<"greet", () => any>'.
//   Types of property 'greet' are incompatible.
//     Type '(name: string) => string' is not assignable to type '() => any'.ts(2345)
fooTobias(obj, 'greet');

您不需要Args参数,只需使用Parameters键入 rest 参数即可。 您还可以使用元组来说服 Typescript 参数是绑定在一起的


type FunctionKeys<Target> = keyof {
    [K in keyof Target as Target[K] extends ((...args: any) => any) ? K : never]: Target[K]
  }
function foo<
  Target extends Record<Key, (...args: any) => any>,
  Key extends FunctionKeys<Target>,
>(...[target, key, ...args]: [Target, Key, ...Parameters<Target[Key]>]): ReturnType<Target[Key]> {
  // typeof method === 'Target[Key]'
  const method = target[key];
  return method(...args);
}

游乐场链接

您可以像这样组合 function 的泛型类型:

function foo<
  Target extends Record<Key, (...args: Args) => any>,
  Args extends unknown[],
  Key extends PropertyKey
>(target: Target, key: Key, ...args: Args): ReturnType<Target[Key]> 
{
  const method = target[key];
  return method(...args);
}

魔法发生在Target的约束中。 我们将其限制为Record ,其中键的类型为Key 值类型是 function,参数类型为Args function 的返回类型是使用实用程序类型ReturnType键入的。

Arg使用通用类型还允许在 function 实现中实现类型安全。 这将是错误的:

function foo<
  Target extends Record<Key, (...args: Args) => any>,
  Args extends unknown[],
  Key extends PropertyKey
>(target: Target, key: Key, ...args: Args): ReturnType<Target[Key]> {
  const method = target[key];

  
  method("abc")
  //     ~~~~~ Argument of type '[string]' is not assignable to 
  //           parameter of type 'Args'

  return 123
  //     ~~~ Type 'number' is not assignable to type 'ReturnType<Target[Key]>'
}

这就是调用 function 的样子:

// valid
const result = foo(obj, "c", "Peter");

// invalid
foo(obj, "a")
foo(obj, "c", 123)

操场

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM