我们已经定义了一个基类,它将被多次子类化.我们希望在基类上定义一个exec方法,该方法接受派生类上方法的名称和参数,然后调用它.

但是,我们遇到了派生类方法的参数类型化问题.以下是演示该错误的一段代码:

class BaseClass {
    exec<
        Self extends Record<MethodKey, (...args: any) => void>,
        MethodKey extends keyof Self,
        Method extends Self[MethodKey],
    >(
        this: Self,
        methodKey: MethodKey,
        ...args: Parameters<Method>
    ) {
        this[methodKey](...args);
    }
}

class MyClass extends BaseClass {   
    constructor() {
        super();
        this.exec(`sayMessage`, `Hello`); // Error: Argument of type '["Hello"]' is not assignable to parameter of type 'Parameters<this["sayMessage"]>'.
    }

    sayMessage(message: string) {
        console.log(message);
    }
}

TS Playground link

为什么这不起作用,我们如何正确地键入exec方法,以便它接受另一个方法的参数?

推荐答案

这里的主要问题是,为Self推断的类型实际上是polymorphic this type,这是当前类实例类型的隐式generic类型constrained.由于the Parameters utility type被实现为conditional type,这意味着args是类型Parameters<this["sayMessage"]>,即generic conditional type.编译器不知道什么值可以或不可以赋值给这样的类型,因为它往往只进行defer次求值.在它确切知道this是什么之前,它不会冒险猜测Parameters<this["sayMessage"]>可能是什么.因此,它失败了.

如果您给编译器提供它应该推断的确切类型的值,那么在编译器上就容易多了.因此,与其说args是什么复杂的东西,不如让它成为自己的泛型类型参数A,并try 根据它重写其他东西.例如:

class BaseClass {
    exec<
        A extends any[],
        K extends PropertyKey
    >(
        this: Record<K, (...args: NoInfer<A>) => void>,
        methodKey: K,
        ...args: A
    ) {
        this[methodKey](...args);
    }
}

这与您的非常相似,除了现在我们只有两个泛型类型参数.有A个,被限制为列表类型,K,对应于您的MethodKey,被限制为PropertyKey,这只是一个实用程序类型,等同于"类键"类型,string | number | symbol.

然后对于this parameter,我们说它是Record<K, (...args: NoInfer<A>) => void>型的.这本质上只是"具有键K的属性的东西,它是一个接受类型A的Arg列表的函数",除非我们真的不希望编译器try 从this推断A.不会有结果的.因此,我们使用新的NoInfer utility type introduced in TypeScript 5.4来告诉编译器不要try .(5.3及更低版本有解决方法,但我并不担心这些问题).

现在,当你叫它的时候,它就起作用了.编译器很高兴地看到this属于类似{sayMessage: (...args: string)=>void}的类型,并且它根本不需要try 处理泛型条件类型:

class MyClass extends BaseClass {
    constructor() {
        super();
        this.exec(`sayMessage`, "hello"); // okay
    }

    sayMessage(message: string) {
        console.log(message);
    }
}

Playground link to code

Javascript相关问答推荐

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

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

Chrome是否忽略WebAuthentication userVerification设置?""

为什么ngModel不能在最后一个版本的Angular 17上工作?'

在Vite React库中添加子模块路径

切换时排序对象数组,切换不起作用

未捕获错误:[]:getActivePinia()被调用,但没有活动Pinia.🍍""在调用app.use(pinia)之前,您是否try 使用store ?""

实现JS代码更改CSS元素

使用Java脚本导入gltf场景并创建边界框

WhatsApp Cloud API上载问题:由于MIME类型不正确而导致接收&Quot;INVALID_REQUEST";错误

Jest toHaveBeenNthCalledWith返回当前设置的变量值,而不是调用时的值

回溯替代方式

如何使用基于promise (非事件emits 器)的方法来传输数据?

Reaction-SWR-无更新组件

传递方法VS泛型对象VS事件特定对象

React:防止useContext重新渲染整个应用程序或在组件之间共享数据而不重新渲染所有组件

如何正确地在ComponentWillUnmount中卸载状态以避免内存泄漏?

设置复选框根据选中状态输入选中值

当S点击按钮时,我如何才能改变它的样式?

我如何才能让p5.js在不使用实例模式的情况下工作?