typescript 如何在JavaScript中创建一个`Callable`对象,而不继承`Function`,并且没有类型错误?

cvxl0en2  于 2023-04-22  发布在  TypeScript
关注(0)|答案(1)|浏览(160)

正如this blog post和这篇堆栈溢出文章中所述,可以按如下方式使对象可调用:

class Callable extends Function {
    constructor() {
        super();
        var closure = function (...args) { return closure._call(...args) }
        return Object.setPrototypeOf(closure, new.target.prototype)
    }

    _call(...args) {
        console.log(this, args)
    }
}

然而,这会导致CSP(Content-Security-Policy)的问题,比如在Chrome扩展中运行时。可以删除extends Functionsuper()调用,但这会导致类型错误。对于我的用例,不幸的是,我不能忽略这一点。如果有一种方法可以覆盖类型错误(例如,使用JSDoc),那也可以工作。
示例用法应该如下所示:

class Pipeline extends Callable {
    _call(...args) {
        // Override _call method here
    }
}

// Create new pipeline
const pipe = new Pipeline();

// Call the pipeline
// NOTE: This is where the type error happens if the class is not inherited from `Function`. Something along the lines of:
// This expression is not callable. Type 'Pipeline' has no call signatures.
const result = pipe("data");

任何帮助将不胜感激。

hgb9j2n6

hgb9j2n61#

TypeScript目前并没有对classconstructor方法返回值时发生的情况进行建模。microsoft/TypeScript#27594上有一个功能请求来支持这一点,但目前TypeScript只会将类示例类型识别为具有其声明的成员,JavaScript不允许你声明类示例的调用签名。因此,即使在运行时,Callable子类的示例由于返回覆盖和原型杂耍而可调用,TypeScript也无法看到它。
如果你使用的是TypeScript文件而不是JSDoc,我可能会建议将所需的调用签名mergingCallable接口中,如下所示:

class Callable {
    constructor() {
        var closure: any = function (...args: any) { return closure._call(...args) }
        return Object.setPrototypeOf(closure, new.target.prototype)
    }
    _call(...args: any) {
        console.log(this, args)
    }
};

interface Callable {
    (...arg: any): void;
}

你可以在工作中看到:

const x = new Callable();
x("a", "b", "c"); // okay
class Pipeline extends Callable {
    _call(...args: any) { console.log("overridden"); super._call(...args) }
}
const pipe = new Pipeline();
const result = pipe("data"); // okay
// "overridden"
// [object Function],  ["data"]

Playground链接到TS代码
不幸的是,您不能在JSDoc中定义interface(请参阅microsoft/TypeScript#33207上的特性请求),虽然这可能适用于多个文件或不同格式,但我将忘记这种方法。
相反,您可以使用类型Assert或等效的JSDoc来告诉编译器Callable的类型是

new () => {
  (...args: any[]): void, 
  _call(...args: any[]): void
}

这意味着它有一个构造签名,返回一个带有_call方法的可调用对象。它看起来像这样:

/** @type {new () => {(...args: any[]): void, _call(...args: any[]): void}}*/
const Callable = /** @type {any} */ (class {
    constructor() {
        /** @type {any} */
        var closure = function (/** @type {any[]} */...args) { return closure._call(...args) }
        return Object.setPrototypeOf(closure, new.target.prototype)
    }
    _call(/** @type {any[]} */...args) {
        console.log(this, args)
    }
});

您可以验证它是否有效:

const x = new Callable();
x("a", "b", "c"); // okay
class Pipeline extends Callable {
    _call(/** @type {any[]} */ ...args) {
        console.log("overridden"); super._call(...args)
    }
}
const pipe = new Pipeline();
const result = pipe("data"); // okay 
// "overridden"
// [object Function], ["data"]

Playground链接到JSDoc注解的JS代码
所以这是一种适用于JSDoc的方法。我不打算评论让TypeScript支持可调用类示例的总体目标是否值得追求,除了说这样的事情可能会导致大多数JavaScript/TypeScript用户都不会预料到的令人惊讶的边缘情况行为。显然,用例将推动这样的决定,但仍然应该谨慎行事。

相关问题