我正在try 在类型脚本中创建一个函数,该函数接受函数的对象文字,并返回具有非常相似签名的对象文字,只有第一个参数已被删除.这里的目标是完全保留类型,因为很容易返回对象文本,其中成员的函数的参数是未知的参数array.

目标如下:

const res = doSomething('someVal', { func1: (proxy, other: string) => proxy + other });
res.func1('other string') // This knows the "other" param is a string, and returns a string.

我可以创建一个编译的结果,但输出文字的函数都是参数数组,或者我得到了某种编译器错误.以下是我的众多try 之一.

type Proxy<T> = {
  value: T;
};

/* 
 * The first parameter is always Proxy<T>.
 * The remaining parameters are what I want to keep in the return function.
 */
export type ProxyMethod<T, F extends (signalProxy: Proxy<T>, ...args: readonly any[]) => any> =
  F extends (...args: infer P) => infer R
    ? (args: P) => R
    : never;

/** This is the type of the object literal of functions that is the input */
export type ProxyMethods<T, K extends string> = {
  [Key in K]: ProxyMethod<T, (...args: unknown[]) => unknown>
};

/** Takes a ProxyMethod and creates a function without the first param. */
export type OutputMethod<F extends ProxyMethod<any, () => any>> =
  F extends ProxyMethod<infer T, infer F2>
    ? F2 extends ((signalProxy: Proxy<T>, ...args: infer P) => infer R)
      ? (...args: P) => R : never
    : never;

/** The intended output object literal */
export type OutputMethods<M extends ProxyMethods<any, string>> = { [Key in keyof M]: OutputMethod<M[Key]> };

function doSomething<T, const M extends Record<string, ProxyMethod<T, any>>>(
  value: T,
  methods: M
): OutputMethods<M> {
  /* do something */
  return methods as any; // stub, all I care about is the typings.
}

// Target signature provides too few arguments
doSomething('test', { help: (proxy: Proxy<string>, a: number) => `${a}`});

Playground

推荐答案

我会这样做:

function doSomething<T, M extends Record<keyof M, (p: Proxy<T>, ...args: any) => any>>(
  value: T,
  methods: M
): { [K in keyof M]:
    M[K] extends (p: Proxy<T>, ...args: infer A) => infer R ? (...args: A) => R : never
  } {
  return methods as any;
}

这里,doSomething()value参数的类型T中是generic,在methods参数的类型M中是generic,其中Mconstrained,具有可分配给(p: Proxy<T>, ...args: any) => any的属性;也就是说,对于第一个参数是类型Proxy<T>的所有BE方法.(表格X extends Record<keyof X, Y>中的recursive constraint代表"X的所有属性都应为Y类型".)

则输出类型为mapped type,其中每个方法类型都被转换为不带第一个参数的版本.它使用conditional type inference来提取所需的参数列表A和返回类型R.

让我们来测试一下:

const y = doSomething('test',
  { help: (proxy: Proxy<string>, a: number) => `${a}` }
);
/* const y: {
    help: (a: number) => string;
} */

看上go 不错.


遗憾的是,您无法将回调参数设置为contextually typed;如果省略type annotation,您将得到隐含的any错误:

const y = doSomething('test',
  { help: (proxy, a: number) => `${a}` } // error!
  // ----> ~~~~~  Parameter 'proxy' implicitly has an 'any' type.
);

TypeScrip的推理算法不能在这样的映射类型中同时进行泛型类型推断和上下文类型输入,至少目前不能.参见microsoft/TypeScript#52269,它链接到microsoft/TypeScript#47599,请求对这类事情进行全面改进.

目前,如果您希望同时进行泛型类型参数推理和回调参数推理,则需要以某种方式将它们设置为非同步.要做到这一点,一种方法是转到curry-doSomething.因此,我们用Currate函数value => methods => result替换单个函数(value, methods) => result:

function doSomething<T>(value: T) {
  return function <M extends Record<keyof M, (p: Proxy<T>, ...args: any) => any>>(
    methods: M
  ): { [K in keyof M]:
      M[K] extends (p: Proxy<T>, ...args: infer A) => infer R ? (...args: A) => R : never
    } {
    return methods as any;
  }
}

这让编译器首先推断T,then根据上下文推断回调参数类型:

const y = doSomething('test')({ help: (proxy, a: number) => `${a}` }); // okay

Playground link to code

Typescript相关问答推荐

类型断言添加到类型而不是替换

创建一个根据布尔参数返回类型的异步函数

TypeScrip:基于参数的条件返回类型

为什么我的Set-Cookie在我执行下一次请求后被擦除

使用axios在ngOnInit中初始化列表

无法从Chrome清除还原的初始状态数据

为什么在回调函数的参数中不使用类型保护?

使用订阅时更新AG网格中的行

如何在处理数组时缩小几个派生类实例之间的类型范围?

如何使所有可选字段在打印脚本中都是必填字段,但不能多或少?

使用Redux Saga操作通道对操作进行排序不起作用

重写返回任何

为什么看起来相似的代码在打字时会产生不同的错误?

从联合提取可调用密钥时,未知类型没有调用签名

如何键入';v型';

通过辅助函数获取嵌套属性时保留类型

定义一个只允许来自另一个类型的特定字符串文字的类型的最佳方式

如何从联合类型推断函数参数?

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?

如果父级被删除或删除,如何自动删除子级?