我正在用TypeScript重写一个旧的浏览器扩展,遇到了一个非常古怪的"方便"函数,它旨在包装另一个函数,以有效地"挂钩"到该函数.当我打算在生产代码中替换这个函数时,我很好奇是否有可能以满足TS的方式编写这个函数,因为我的引擎一直在抱怨.

这是我得到的(主观上)最好的结果

type GetReturnType<Type> = Type extends (...args: never[]) => infer Return ? Return : never;
type GetArgsType<Type> = Type extends (...args: infer Args) => any ? Args : never

export function wrapFunction<T, K extends keyof T>(
    parent: T,
    name: K,
    newFunction: (
        oldFn: T[K],
        ...args: GetArgsType<T[K]>
    ) => GetReturnType<T[K]>
) {
    var oldFunction = parent[name]
    parent[name] = (...args:GetArgsType<T[K]>) => {
        return newFunction(oldFunction, ...args)
    }
    return parent[name]
}

在此情况下,我的发动机在从parent[name] = ...开始的生产线上发出以下投诉

Type '(...args: GetArgsType<T[K]>) => GetReturnType<T[K]>' is not assignable to type 'T[K]'.
  'T[K]' could be instantiated with an arbitrary type which could be unrelated to '(...args: GetArgsType<T[K]>) => GetReturnType<T[K]>'.

推荐答案

众所周知,TypeScript在推理conditional types个依赖于generic个类型参数的类型方面很糟糕. 您的GetReturnTypeGetArgsType实用程序类型是作为条件类型实现的(并且,为了它的价值,类似于已经可用的ReturnTypeParameters实用程序类型).在wrapFunction()函数中,类型TK是泛型类型参数,因此GetReturnType<T[K]>GetArgsType<T[K]>是泛型条件类型. 因此,我们可以预期编译器无法验证哪些值可以分配给它们.

有时条件泛型类型是实现某些功能的最佳方式,我们必须解决它,但其他时候您可以重构以完全避免它们. 在你的例子中,我们不需要从函数类型T[K]开始,然后从中找出arg列表类型和返回类型,让我们从arg列表类型A和返回类型R开始.我们还需要Kname个. 现在我们可以用ARK重写所有类型,如下所示:

function wrapFunction<K extends PropertyKey, A extends any[], R>(
  parent: Record<K, (...a: A) => R>,
  name: K,
  newFunction: (
    oldFn: (...a: A) => R,
    ...args: A
  ) => R
): (...a: A) => R {
  var oldFunction = parent[name]
  parent[name] = (...args: A) => {
    return newFunction(oldFunction, ...args)
  }
  return parent[name]
}

类型T原本应该是"一个对象,其属性的键是类型K,其值是我们关心的函数类型",因此我们可以使用the Record utility type(mapped type,不是条件类型)将其写成Record<K, (...a: A) => R>.

你可以看到一切都编译没有错误. 现在parent[name]的类型是(...a: A) => R,所以在给它分配箭头函数时没有任何抱怨.

Playground link to code

Javascript相关问答推荐

如何解决这个未能在响应上执行json:body stream已读问题?

vscode扩展-webView Panel按钮不起任何作用

微软Edge编辑和重新发送未显示""

在JavaScript中声明自定义内置元素不起作用

Exceljs:我们在file.xlsx(...)&#中发现了一个问题'"" 39人;

如何为我的astro页面中的相同组件自动创建不同的内容?

构造HTML表单以使用表单数据创建对象数组

为什么Mutations 观察器用微任务队列而不是macrotask队列处理?

如何根据当前打开的BottomTab Screeb动态加载React组件?

如何将react—flanet map添加到remixjs应用程序

提交链接到AJAX数据结果的表单

如何将未排序的元素追加到数组的末尾?

Reaction组件在本应被设置隐藏时仍显示

OpenAI转录API错误请求

Google脚本数组映射函数横向输出

如何使用Reaction路由导航测试挂钩?

使用Library chart.js在一个带有两个y轴的图表中绘制两个数据集

由于http.get,*ngIf的延迟很大

CSS网格使页面自动滚动

鼠标进入,每秒将图像大小减小5%