Typescript:在类构造函数中使用`this`类型

4
我有一个包含构造函数的类,该构造函数定义了类的属性。我需要使构造函数接受一个仅包含(扩展)类成员的对象。
abstract class A {
    constructor(initializer: Partial<this>) { // <-- using this type in a constructor is not allowed!
        for (const [key,value] of Object.entries(initializer)) {
            Object.defineProperty(this, key, { value, enumerable: true })
        }
    }
    static someStaticMethod() {}
    someInstancemethod() {}
    
}

这个类仅用于被其他类扩展。

class B extends A {
    a?: string
    b?: number
    c?: boolean
}

new B({ a: "hello", b: 1, c: true, d: "this must error" }) // <-- I want only properties of B here in the constructor argument

我知道可以给 A 添加类型参数,然后将扩展类分配给它

abstract class A<T> {
    constructor(initializer: Partial<T>) {
        ...
    }
}

class B extends A<B> {
...
}

但是,这种重复 B 的解决方案看起来不太优雅,尤其考虑到这是我正在开发的库的外部 API。有可能实现我想要的效果,并保留第一个示例中的语法 class B extends A 吗?如果不行,有什么可行的解决方案吗? Playground 链接


1
B作为通用参数重复使用是一种相当标准的模式,称为奇异递归模板模式。我同意这不是最优雅的解决方案,但它是相当标准的。 - Silvio Mayolo
1
...嗯,你打算如何在子类中使用privatePartial<B>无法访问abc属性。当你写下“我知道我可以向A添加一个类型参数,然后将扩展类分配给它”时,你尝试过了吗?在那里无法传递abc。你能否在你的问题中解决这个问题,使之只有一个障碍而不是多个? - jcalz
谢谢。这个解决方法对你有用吗?你可以在静态方法中模拟this,但不能在构造函数本身中使用。如果这回答了你的问题,我可以写一篇文章并附上相关文档来解释它。如果不是,请问我漏掉了什么? - jcalz
谢谢@jcalz,这回答了我的问题。如果您能在您的答案中包含一些关于为什么ts不支持此功能的提示,我将不胜感激。 - soffyo
除了指向ms/TS#38038并耸肩之外,我如何权威地回答“为什么”它不受支持? 我可以猜测它未被实现,是因为构造函数处于唯一位置,即静态方法的'this'上下文是实例而不是构造函数本身,因此要支持它需要一些有意识的努力,但这还没有发生。 虽然如此,这只是一个猜测。 除非有文档记录为什么不支持它,否则我就不知道该怎么回答。 这似乎也超出了所提问的范围。 - jcalz
显示剩余2条评论
1个回答

2
目前在TypeScript中,无法在类的构造函数方法constructor()的调用签名中使用this类型。有一个开放的功能请求microsoft/TypeScript#38038要求支持这种功能,但现在它还不是语言的一部分。
(有人问为什么还不支持,但我没有权威的答案。我猜想:构造函数方法是唯一的静态方法,其this上下文是类实例的上下文,因此实现其调用签名的this类型可能需要一些有意识的努力。同时,这也不是社区迫切需要的功能,如上面链接问题的相对较少的赞数所示。这些可能是它还不被支持的原因之一。但是,正如我所说,这些只是猜测。)
直到实施了这个,否则你需要使用解决方法。
其中一个解决方法是使用 一个 static 方法this 类型代替构造函数,因此你可以调用 B.make({}) 而不是 new B({})
不幸的是,this 类型也不支持静态方法。支持已经在microsoft/TypeScript#5863上请求。但是,你可以通过 this 参数泛型 类型参数来模拟这种行为,如 在该 GitHub 问题评论中提到的那样
abstract class A {
    static make<T extends A>(
      this: new (initializer: Partial<A>) => T, 
      initializer: Partial<T>
    ) {
        return new this(initializer);
    }
    constructor(initializer: Partial<A>) {
        for (const [key, value] of Object.entries(initializer)) {
            Object.defineProperty(this, key, { value, enumerable: true })
        }
    }
    static someStaticMethod() { }
    someInstancemethod() { }

}

make()”方法只能在接受 Partial<A> 且生成类型为 T 且限制为 A 的实例的具体构造函数上调用。因此,您不能直接调用 A.make(),原因与无法调用 new A() 相同,因为构造函数是抽象的。”
A.make({}) // error!
// <-- Cannot assign an abstract constructor type to a non-abstract constructor type

但是您可以根据需要进行子类化,并且静态的make()方法将被继承:
class B extends A {
    a?: string
    b?: number
    c?: boolean
}

当您调用B.make()时,编译器会推断TB,因此make()的参数是Partial<B>,其中包括拒绝excess properties等内容。
const b = B.make({ a: "hello", b: 1, c: true }); // okay
// const b: B
console.log(b); // {a: "hello", b: 1, c: true}

B.make({ a: "hello", b: 1, c: true, d: "this must error" }); // error!
// -------------------------------> ~~~~~~~~~~~~~~~~~~~~
// Object literal may only specify known properties

代码操场链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接