在TypeScript类上调用构造函数,而无需新建 [英] Call constructor on TypeScript class without new

查看:118
本文介绍了在TypeScript类上调用构造函数,而无需新建的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在JavaScript中,我可以定义一个构造函数,可以在有或没有 new 的情况下调用该函数:

In JavaScript, I can define a constructor function which can be called with or without new:

function MyClass(val) {
    if (!(this instanceof MyClass)) {
        return new MyClass(val);
    }

    this.val = val;
}

然后我可以构造 MyClass 使用以下语句之一的对象:

I can then construct MyClass objects using either of the following statements:

var a = new MyClass(5);
var b = MyClass(5);

我尝试使用下面的TypeScript类实现类似的结果:

I've tried to achieve a similar result using the TypeScript class below:

class MyClass {
    val: number;

    constructor(val: number) {
        if (!(this instanceof MyClass)) {
            return new MyClass(val);
        }

        this.val = val;
    }
}

但调用 MyClass(5 )给我错误'typeof MyClass'类型的值不可调用。

有什么办法可以使这种模式在TypeScript中起作用?

Is there any way I can make this pattern work in TypeScript?

推荐答案

这是怎么回事?描述 MyClass 的所需形状及其构造函数:

What about this? Describe the desired shape of MyClass and its constructor:

interface MyClass {
  val: number;
}

interface MyClassConstructor {
  new(val: number): MyClass;  // newable
  (val: number): MyClass; // callable
}

注意 MyClassConstructor 被定义为既可以作为函数调用,也可以作为构造函数更新。然后实现它:

Notice that MyClassConstructor is defined as both callable as a function and newable as a constructor. Then implement it:

const MyClass: MyClassConstructor = function(this: MyClass | void, val: number) {
  if (!(this instanceof MyClass)) {
    return new MyClass(val);
  } else {
    this!.val = val;
  }
} as MyClassConstructor;

以上方法虽然有一些小皱纹,但仍然可以使用。皱纹之一:实现返回 MyClass |未定义,并且编译器没有意识到 MyClass 返回值对应于可调用函数,而未定义值对应于可更新的构造函数...因此它抱怨。因此,最后是 as MyClassConstructor 。皱纹二: this 参数目前并不狭窄通过控制流分析,因此我们必须断言在设置其<$时, this 不是 void c $ c> val 属性,即使那时我们知道它不能 void 。因此,我们必须使用非空断言运算符

The above works, although there are a few small wrinkles. Wrinkle one: the implementation returns MyClass | undefined, and the compiler doesn't realize that the MyClass return value corresponds to the callable function and the undefined value corresponds to the newable constructor... so it complains. Hence the as MyClassConstructor at the end. Wrinkle two: the this parameter does not currently narrow via control flow analysis, so we have to assert that this is not void when setting its val property, even though at that point we know it can't be void. So we have to use the non-null assertion operator !.

无论如何,您可以验证以下各项是否有效:

Anyway, you can verify that these work:

var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass

希望有帮助;

注意:如前所述@Paleo的答案(如果您的目标是ES2015或更高版本),请使用 class 在您的源代码中将在编译的JavaScript中输出 class ,而那些 require new()根据规格。我见过类似 TypeError之类的错误:如果没有 new ,则无法调用类构造函数。一些JavaScript引擎很可能会忽略该规范,并且也会很乐意接受函数样式的调用。如果您不关心这些警告(例如,您的目标明确是ES5,或者您知道要在那些不符合规范的环境中运行),那么您绝对可以强制TypeScript遵循:

Caveat: as mentioned in @Paleo's answer, if your target is ES2015 or later, using class in your source will output class in your compiled JavaScript, and those require new() according to the spec. I've seen errors like TypeError: Class constructors cannot be invoked without 'new'. It is quite possible that some JavaScript engines ignore the spec and will happily accept function-style calls also. If you don't care about these caveats (e.g., your target is explicitly ES5 or you know you're going to run in one of those non-spec-compliant environments), then you definitely can force TypeScript to go along with that:

class _MyClass {
  val: number;

  constructor(val: number) {
    if (!(this instanceof MyClass)) {
      return new MyClass(val);
    }

    this.val = val;
  }
}
type MyClass = _MyClass;
const MyClass = _MyClass as typeof _MyClass & ((val: number) => MyClass)

var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass

在这种情况下,您已将 MyClass重命名为替换为 _MyClass ,并将 MyClass 定义为两种类型(与 _MyClass )和一个值(与 _MyClass 构造函数相同,但其类型被断言也像函数一样可调用) 。)这在编译时有效,如上所示。您的运行时是否满意取决于上面的警告。就我个人而言,我会坚持使用原始答案中的函数样式,因为我知道在es2015及更高版本中它们既可以调用又可以更新。

In this case you've renamed MyClass out of the way to _MyClass, and defined MyClass to be both a type (the same as _MyClass) and a value (the same as the _MyClass constructor, but whose type is asserted to also be callable like a function.) This works at compile-time, as seen above. Whether your runtime is happy with it is subject to the caveats above. Personally I'd stick to the function style in my original answer since I know those are both callable and newable in es2015 and later.

再次祝你好运!

如果您只是想通过bindNew()函数类型的方法https://stackoverflow.com/a/48326964/738768>此答案,它采用符合规范的,并生成可更新且像函数一样可调用,您可以执行以下操作:

If you're just looking for a way of declaring the type of your bindNew() function from this answer, which takes a spec-conforming class and produces something which is both newable and callable like a function, you can do something like this:

function bindNew<C extends { new(): T }, T>(Class: C & {new (): T}): C & (() => T);
function bindNew<C extends { new(a: A): T }, A, T>(Class: C & { new(a: A): T }): C & ((a: A) => T);
function bindNew<C extends { new(a: A, b: B): T }, A, B, T>(Class: C & { new(a: A, b: B): T }): C & ((a: A, b: B) => T);
function bindNew<C extends { new(a: A, b: B, d: D): T }, A, B, D, T>(Class: C & {new (a: A, b: B, d: D): T}): C & ((a: A, b: B, d: D) => T);
function bindNew(Class: any) {
  // your implementation goes here
}

这具有正确键入以下内容的作用:

This has the effect of correctly typing this:

class _MyClass {
  val: number;

  constructor(val: number) {    
    this.val = val;
  }
}
type MyClass = _MyClass;
const MyClass = bindNew(_MyClass); 
// MyClass's type is inferred as typeof _MyClass & ((a: number)=> _MyClass)

var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass

但要注意 bindNew()不适用于所有可能的情况。具体来说,它适用于最多包含三个必需参数的构造函数。具有可选参数或多个重载签名的构造函数可能无法正确推断。因此,您可能必须根据用例来调整类型。

But beware the the overloaded declarations for bindNew() don't work for every possible case. Specifically it works for constructors which take up to three required parameters. Constructors with optional paramaters or multiple overload signatures will probably not be properly inferred. So you might have to tweak the typings depending on use case.

好的,希望 会有所帮助。第三次好运。

Okay, hope that helps. Good luck a third time.

TypeScript 3.0引入了元组处于静止和展开位置,使我们能够轻松处理函数任意数量和类型的参数,没有上述重载和限制。这是 bindNew()的新声明:

TypeScript 3.0 introduced tuples in rest and spread positions, allowing us to easily deal with functions of an arbitrary number and type of arguments, without the above overloads and restrictions. Here's the new declaration of bindNew():

declare function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
  Class: C & { new(...args: A): T }
): C & ((...args: A) => T);

这篇关于在TypeScript类上调用构造函数,而无需新建的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆