Typescript: "esto" polimórfico para miembros estáticos

Creado en 1 dic. 2015  ·  150Comentarios  ·  Fuente: microsoft/TypeScript

Cuando intentamos implementar un sistema de modelo de estilo de registro activo bastante básico, pero polimórfico, nos encontramos con problemas con el sistema de tipo que no respeta this cuando se usa junto con un constructor o plantilla/genérico.

He publicado antes sobre esto aquí, #5493, y #5492 parece mencionar también este comportamiento.

Y aquí hay una publicación SO que hice:
http://stackoverflow.com/questions/33443793/create-a-generic-factory-in-typescript-unsolved

He reciclado mi ejemplo de #5493 en este ticket para mayor discusión. Quería un boleto abierto que representara el deseo de tal cosa y de discusión pero los otros dos están cerrados.

Aquí hay un ejemplo que describe un modelo Factory que produce modelos. Si desea personalizar el BaseModel que regresa del Factory , debería poder anularlo. Sin embargo, esto falla porque this no se puede usar en un miembro estático.

// Typically in a library
export interface InstanceConstructor<T extends BaseModel> {
    new(fac: Factory<T>): T;
}

export class Factory<T extends BaseModel> {
    constructor(private cls: InstanceConstructor<T>) {}

    get() {
        return new this.cls(this);
    }
}

export class BaseModel {
    // NOTE: Does not work....
    constructor(private fac: Factory<this>) {}

    refresh() {
        // get returns a new instance, but it should be of
        // type Model, not BaseModel.
        return this.fac.get();
    }
}

// Application Code
export class Model extends BaseModel {
    do() {
        return true;
    }
}

// Kinda sucks that Factory cannot infer the "Model" type
let f = new Factory<Model>(Model);
let a = f.get();

// b is inferred as any here.
let b = a.refresh();

Tal vez este problema sea tonto y haya una solución fácil. Agradezco comentarios sobre cómo se puede lograr ese patrón.

In Discussion Suggestion

Comentario más útil

¿Hay alguna razón por la que se cerró este problema?

El hecho de que esto polimórfico no funcione en estática básicamente hace que esta función DOA, en mi opinión. Hasta la fecha, nunca he necesitado esto polimórfico en los miembros de la instancia, pero he necesitado cada pocas semanas estáticas, ya que el sistema de manejo de estáticas se finalizó en los primeros días. Me llenó de alegría cuando se anunció esta característica, y luego me decepcioné cuando me di cuenta de que solo funciona en los miembros de la instancia.

El caso de uso es muy básico y extremadamente común. Considere un método de fábrica simple:

class Animal
{
    static create(): this
    {
        return new this();
    }
}

class Bunny extends Animal
{
    hop()
    {
    }
}

Bunny.create().hop() // Type error!! Come on!!

En este punto, he estado recurriendo a la conversión fea o tirando basura a los métodos static create() en cada heredero. No tener esta función parece un gran vacío de integridad en el lenguaje.

Todos 150 comentarios

¡El tamaño y la forma de mi barco son bastante similares! ¡Ay!

Un método de fábrica en una superclase devuelve nuevas instancias de sus subclases. La funcionalidad de mi código funciona pero requiere que emita el tipo de retorno:

class Parent {
    public static deserialize(data: Object): any { ... create new instance ... }
    // Can't return a this type from statics! ^^^ :(
}

class Child extends Parent { ... }

let data = { ... };
let aChild: Child = Child.deserialize(data);
//           ^^^ Requires a cast as type cannot be inferred.

¡Me encontré con este problema hoy también!

Una solución de reparación es pasar el tipo secundario como un genérico que extiende la clase base, que es una solución que aplico por el momento:

class Parent {
    static create<T extends Parent>(): T {
        let t = new this();

        return <T>t;
    }
}

class Child extends Parent {
    field: string;
}

let b = Child.create<Child>();

¿Hay alguna razón por la que se cerró este problema?

El hecho de que esto polimórfico no funcione en estática básicamente hace que esta función DOA, en mi opinión. Hasta la fecha, nunca he necesitado esto polimórfico en los miembros de la instancia, pero he necesitado cada pocas semanas estáticas, ya que el sistema de manejo de estáticas se finalizó en los primeros días. Me llenó de alegría cuando se anunció esta característica, y luego me decepcioné cuando me di cuenta de que solo funciona en los miembros de la instancia.

El caso de uso es muy básico y extremadamente común. Considere un método de fábrica simple:

class Animal
{
    static create(): this
    {
        return new this();
    }
}

class Bunny extends Animal
{
    hop()
    {
    }
}

Bunny.create().hop() // Type error!! Come on!!

En este punto, he estado recurriendo a la conversión fea o tirando basura a los métodos static create() en cada heredero. No tener esta función parece un gran vacío de integridad en el lenguaje.

@ paul-go, ¿el problema no está cerrado ...?

@ paul-go También me ha frustrado este problema, pero la siguiente es la solución alternativa más confiable que he encontrado. Cada subclase Animal necesitaría llamar a super.create() y simplemente convertir el resultado en su tipo. No es gran cosa y es una sola línea que se puede quitar fácilmente con esto se agrega.

El compilador, intellisense y, lo que es más importante, el conejito están felices.

class Animal {
    public static create<T extends Animal>(): T {
        let TClass = this.constructor.prototype;
        return <T>( new TClass() );
    }
}

class Bunny extends Animal {    
    public static create(): Bunny {
        return <Bunny>super.create();
    }

    public hop(): void {
        console.log(" Hoppp!! :) ");
    }
}

Bunny.create().hop();

         \\
          \\_ " See? I am now a happy Bunny! "
           (')   " Don't be so hostile! "
          / )=           " :P "
        o( )_


@RyanCavanaugh Ups... por alguna razón confundí esto con el #5862... perdón por la agresión del hacha de batalla :-)

@ Think7 Sí ... de ahí el "recurrir a métodos feos de fundición o tirar basura a los métodos de creación estática () en cada heredero". Sin embargo, es bastante difícil cuando eres un desarrollador de bibliotecas y realmente no puedes obligar a los usuarios finales a implementar un montón de métodos estáticos escritos en las clases que heredan de ti.

ley Me perdí totalmente todo bajo tu código :D

Meh valió la pena, tengo que dibujar un conejito.

:+1: conejito

:conejo: :corazon:

+1, definitivamente me gustaría ver esto

¿Ha habido alguna actualización de la discusión sobre este tema?

Permanece en nuestra enorme acumulación de sugerencias.

Javascript ya actúa correctamente en dicho patrón. Si TS pudiera seguir también, eso nos ahorraría una gran cantidad de código repetitivo/extra. El "patrón de modelo" es bastante estándar, espero que TS funcione como lo hace JS en esto.

También me gustaría mucho esta característica por las mismas razones de "Modelo CRUD" que todos los demás. Lo necesito en métodos estáticos más que en métodos de instancia.

Esto proporcionaría una solución ordenada al problema descrito en #8164.

Es bueno que haya "soluciones" con anulaciones y genéricos, pero en realidad no están resolviendo nada aquí: el propósito de tener esta función es evitar tales anulaciones/transferencias y crear coherencia con la forma en que regresan this El tipo se maneja en los métodos de instancia.

Estoy trabajando en los tipos para Sequelize 4.0 y utiliza un enfoque en el que subclasifica una clase Model . Esa clase Model tiene innumerables métodos estáticos como findById() etc. que, por supuesto, no devuelven Model sino su subclase, también conocida como this en el contexto estático:

abstract class Model {
    public static tableName: string;
    public static findById(id: number): this { // error: a this type is only available in a non-static member of a class or interface 
        const rows = db.query(`SELECT * FROM ${this.tableName} WHERE id = ?`, [id]);
        const instance = new this();
        for (const column of Object.keys(rows[0])) {
            instance[column] = rows[0][column];
        }
        return instance;        
    }
}

class User extends Model {
    public static tableName = 'users';
    public username: string;    
}

const user = User.findById(1); // user instanceof User

Esto no es posible escribir actualmente. Sequelize es _the_ ORM para Node y es triste que no se pueda escribir. Realmente necesito esta función. La única forma es emitir cada vez que llama a una de estas funciones o anular cada una de ellas, adaptar el tipo de devolución y no hacer nada más que llamar a super.method() .

También está relacionado que los miembros estáticos no pueden hacer referencia a argumentos de tipo genérico: algunos de los métodos toman un objeto literal de atributos para el modelo que podría escribirse a través de un argumento de tipo, pero solo están disponibles para miembros de instancia.

😰 No puedo creer que esto todavía no esté arreglado/agregado...

Podríamos hacer un buen uso de esto:

declare class NSObject {
    init(): this;
    static alloc(): this;
}

declare class UIButton extends NSObject {
}

let btn: UIButton = UIButton.alloc().init();

aquí hay un caso de uso que desearía que funcionara (migrado de https://github.com/Microsoft/TypeScript/issues/9775 que cerré a favor de esto)

Actualmente, los parámetros de un constructor no pueden usar el tipo this en ellos:

class C<T> {
    constructor(
        public transformParam: (self: this) => T // not works
    ){
    }
    public transformMethod(self: this) : T { // works
        return undefined;
    }
}

Esperado: esto estará disponible para los parámetros del constructor.

Un problema que se busca solucionar:

  • ser capaz de basar mi API fluida alrededor de sí misma o alrededor de otra API fluida reutilizando el mismo código:
class TheirFluentApi {
    totallyUnrelated(): TheirFluentApi {
        return this;
    }
}

class MyFluentApi<FluentApi> {
    constructor(
        public toNextApi: (self: this) => FluentApi // let's imagine it works
    ){
    }
    one(): FluentApi {
        return this.toNextApi(this);
    }
    another(): FluentApi {
        return this.toNextApi(this);
    }
}

// self based fluent API;
const selfBased = new MyFluentApi(this => this);
selfBased.one().another();

// foreign based fluent API:
const foreignBased = new MyFluentApi(this => new TheirFluentApi());
foreignBased.one().totallyUnrelated();

Solución alternativa preparada para el futuro:

class Foo {

    foo() { }

    static create<T extends Foo>(): Foo & T {
        return new this() as Foo & T;
    }
}

class Bar extends Foo {

    bar() {}
}

class Baz extends Bar {

    baz() {}
}

Baz.create<Baz>().foo()
Baz.create<Baz>().bar()
Baz.create<Baz>().baz()

De esta forma, cuando TypeScript admita this para miembros estáticos, el código de usuario nuevo será Baz.create() en lugar de Baz.create<Baz>() mientras que el código de usuario anterior funcionará perfectamente. :sonrisa:

¡Esto es realmente necesario! especialmente para DAO que tienen métodos estáticos que devuelven la instancia. La mayoría de ellos definidos en un DAO base, como (guardar, obtener, actualizar, etc.)

Puedo decir que podría causar cierta confusión, tener el tipo this en un método estático que se resuelva en la clase en la que se encuentra y no en el tipo de la clase (es decir: typeof ).

En JS real, llamar a un método estático en un tipo dará como resultado que this dentro de la función sea la clase y no una instancia de ella... por lo que es inconsistente.

Sin embargo, en la intuición de las personas, creo que lo primero que aparece al ver el tipo de retorno this en un método estático es el tipo de instancia...

@shlomiassaf No sería inconsistente. Cuando especifica una clase como tipo de retorno para una función como Usuario, el tipo de retorno será una instancia del usuario. Exactamente lo mismo, cuando define un tipo de devolución de this en un método estático, el tipo de devolución será una instancia de this (una instancia de la clase). Un método que devuelve la clase en sí se puede modelar con typeof this.

@felixfbecker esto es totalmente una cuestión de punto de vista, así es como eliges verlo.

Inspeccionemos lo que sucede en JS, para que podamos inferir la lógica:

class Example {
  myFunc(): this {
    return this; 
  }

  static myFuncStatic(): this {
    return this;   // this === Example
  }
}

new Example().myFunc() //  instanceof Exapmle === true
Example.myFuncStatic() // === Example

Ahora, this en tiempo de ejecución real es el contexto delimitado de la función, esto es exactamente lo que sucede en las interfaces fluidas tipo API y la ayuda polimórfica de esta función al devolver el tipo correcto, que simplemente se alinea con la forma en que funciona JS, Una clase base que devuelve this devuelve la instancia creada por la clase derivada. La característica es en realidad una solución.

Para resumir:
Se espera que un método que devuelve this que se define como parte del prototipo (instancia) devuelva la instancia.

Continuando con esa lógica, se espera que un método que devuelve this que se define como parte del tipo de clase (prototipo) devuelva el contexto acotado, que es el tipo de clase.

Una vez más, sin prejuicios, sin opiniones, hechos simples.

En cuanto a la intuición, me sentiré cómodo si this devueltos por una función estática representan la instancia, ya que está definida dentro del tipo, pero ese soy yo. Otros pueden pensar diferente y no podemos decirles que están equivocados.

El problema es que debe ser posible escribir un método estático que devuelva una instancia de clase y un método estático que devuelva la clase en sí (tipo de esto). Su lógica tiene sentido desde la perspectiva de JS, pero aquí estamos hablando de _tipos_ de retorno, y usar una clase como tipo de retorno (aquí esto) en TypeScript y cualquier otro lenguaje siempre significa la instancia de la clase. Para devolver la clase actual, tenemos el operador typeof.

@felixfbecker ¡ Esto plantea otro problema!

Si el tipo this es el tipo de instancia, es diferente a lo que se refiere la palabra clave this en el cuerpo del método estático. return this produce un tipo de retorno de función de typeof this , ¡lo cual es totalmente extraño!

No, no es. Cuando defines un método como

getUser(): User {
  ...
}

espera recuperar una _instancia_ de Usuario, no la clase de Usuario (para eso es typeof ). Así es como funciona en todos los idiomas escritos. La semántica de tipos es simplemente diferente de la semántica de tiempo de ejecución.

¿Por qué no usar las palabras clave this o static como constructor en función de manipular con una clase secundaria?

class Model {
  static find():this[] {
    return [new this("prop")]; // or new static(...)
  }
}

class Entity extends Model {
  constructor(public prop:string) {}
}

Entity.find().map(x => console.log(x.prop));

Y si comparamos esto con un ejemplo en JS, veremos que funciona correctamente:

class Model {
  static find() { 
    return [new this] 
  }
}

class Entity extends Model {
  constructor(prop) {
    this.prop = prop;
  }
}

Entity.find().map(x => console.log(x.prop))

No puede usar el tipo this en un método estático, creo que esa es la raíz del problema.

@felixfbecker

Considera esto:

class Greeter {
    static getHandle(): this {
        return this;
    }
}

Esta anotación de tipo es intuitiva, pero incorrecta si el tipo this en un método estático es la instancia de la clase. ¡La palabra clave this tiene un tipo de typeof this en un método estático!

Siento que this _debería_ referirse al tipo de instancia, no al tipo de clase, porque podemos obtener una referencia al tipo de clase del tipo de instancia ( typeof X ) pero no al revés ( instancetypeof X ?)

@xealot está bien, ¿por qué no usar la palabra clave static en lugar this ? Pero this en contexto estático JS aún apunta a un constructor.

@izatop sí, el javascript generado funciona (correcta o incorrectamente). Sin embargo, esto no se trata de Javascript. La queja no es que el lenguaje Javascript no permita este patrón, es que el sistema de tipos de Typescript no lo permite.

No puede mantener la seguridad de tipos con este patrón porque Typescript no permite tipos this polimórficos en miembros estáticos. Esto incluye métodos de clase definidos con la palabra clave static y constructor .

Enlace del patio de recreo

@LPGhatguy pero leíste mi comentario anterior, ¿verdad? Si tiene un método con tipo de devolución User , espera return new User(); , no return User; . De la misma manera, un valor de retorno de this debería significar return new this(); en un método estático. En los métodos no estáticos, esto es diferente, pero está claro porque this siempre es un objeto, no podría instanciarlo. Pero al final, básicamente todos estamos de acuerdo en que esta es la mejor sintaxis debido a typeof y que esta función es muy necesaria en TS.

@xealot entiendo que TypeScript no permite this polimórficos, sin embargo, le preguntaré por qué no agregar esta función a TS.

No sé si lo siguiente funcionará para todos los casos de uso de este problema, pero descubrí que usar el tipo this: en métodos estáticos junto con el uso inteligente del sistema de inferencia de tipos permite divertirse mecanografía. Puede que no sea increíblemente legible, pero hace el trabajo sin tener que redefinir métodos estáticos en las clases secundarias.

Funciona con [email protected]

Considera lo siguiente ;

// IModelClass is just here to describe an instanciator
// since we can't use typeof T (unfortunately) with
// the generic type system.
interface IModelClass<T extends Model> {
  new (...a: any[]): T

  // unfortunately, we have to put here again all the typing information
  // of the static members (without static, since we are describing a class, not an instance)

  some_member: string
  create<T extends Model>(this: IModelClass<T>): T
}

class Model {

  // Here we use this with the IModel<T> to force the
  // type system to use T as our current caller.

  static some_member: string

  // When we call Dog.create() below, T is thus resolved
  // to Dog *and stays that way*
  // If typeof worked on generic types (it doesn't), we could have defined this method
  // instead as 
  // static create<T extends Model>(this: typeof T): T { ... }
  static create<T extends Model>(this: IModelClass<T>): T {
    return new this() // whatever you fancy here
  }
}

class Dog extends Model {
  bark() { }
}

class Cat extends Model {
  meow() { }
}

// Everything should be typed here, and we didn't have to redefine static methods
// in Dog nor Cat
let dog = Dog.create()
dog.bark()
let cat = Cat.create()
cat.meow()

@ceymard ¿por qué se da this como parámetro?

Esto se debe a https://github.com/Microsoft/TypeScript/issues/3694 que se envió con TypeScript 2.0.0 que permite definir este parámetro para funciones (permitiendo así el uso de this en su cuerpo sin problemas y también para prevenir el uso incorrecto de las funciones)

Resulta que podemos usarlos tanto en métodos como en métodos estáticos, y que permite cosas realmente divertidas, como usarlo para "ayudar" al inferidor de tipos obligándolo a comparar el this que proporcionamos (IModelo) al que se está utilizando (Perro o Gato). Luego "adivina" el tipo correcto para T y lo usa, escribiendo correctamente nuestra función.

(Tenga en cuenta que this es un parámetro _especial_ entendido por el compilador de TypeScript, no agrega uno más a la función)

Pensé que la sintaxis era algo así como <this extends Whatever> . Entonces, ¿esto todavía funcionaría si create() tiene otros parámetros?

Absolutamente

También quería señalar que esto es importante para los tipos integrados.
Cuando crea una subclase Array , por ejemplo, el método from() de la subclase devolverá una instancia de la subclase, no Array .

class Task {}
class TaskList extends Array<Task> {
  public execute() {}
}
// actually returns instance of TaskList at runtime
const tasks = TaskList.from([new Task()])
tasks.execute() // error, method execute does not exist on type Array

¿Alguna actualización sobre este tema? Esta característica mejoraría enormemente la experiencia de mi equipo con TypeScript.

Tenga en cuenta que esto es bastante crítico, ya que es un patrón de uso común en los ORM y una gran cantidad de código se comporta de manera muy diferente a lo que el compilador y el sistema de tipos creen que debería.

Plantearía algunos contraargumentos contra el contraargumento.

Como señaló @shlomiassaf , permitir this en miembros estáticos causará confusión porque this significa algo diferente en el método estático y el método de instancia. Además, this en la anotación de tipo y this en el cuerpo del método tienen una semántica diferente. Podemos evitar esta confusión introduciendo una palabra clave nueva, pero el costo de una palabra clave nueva es alto.

Y hay una solución fácil en TypeScript actual. @ceymard ya lo ha demostrado . Y preferiría su enfoque porque captura la semántica del tiempo de ejecución del método estático. Considere este código.

class A {
  constructor() {}
  static create<T extends A>(this: {new (): T}) {} // constructor signature is exactly the same as A's
}

class B extends A {
  constructor(a: number) {
    super()
  }
}

B.create() // correctly trigger compile error here

Si se admite esto polimórfico, el compilador debe informar el error en class B extends A . Pero este es un cambio radical.

@HerringtonDarkholme No veo la confusión, el tipo de retorno de this es exactamente lo que esperaría de return new this() . Potencialmente, podríamos usar self para eso, pero introducir otra palabra clave (que no sea la que se usó para generar el resultado) me parece más confuso. Ya lo usamos como tipo de retorno, el único (pero grande) problema es que no es compatible con miembros estáticos.

El compilador no debería requerir que los programadores realicen soluciones alternativas, se supone que TypeScript es un "JavaScript mejorado" y eso no es cierto en este caso, debe realizar una solución compleja para que su código no genere millones de errores. Es bueno que podamos lograrlo en la versión actual de alguna manera, pero eso no significa que esté bien y no necesite una solución.

Entonces, ¿por qué el tipo de devolución de this es diferente del tipo de this en el cuerpo del método?
¿Por qué falla este código? ¿Cómo puedes explicárselo a un recién llegado?

class A {
  static create(): this {
     return this
  }
}

¿Por qué un superconjunto de JavaScript no aceptaría esto?

class A {
  static create() {
    return new this()
  }
}

abstract class B extends A {}

Cierto, es posible que necesitemos introducir otra palabra clave entonces. Podría ser self , o tal vez instance

@HerringtonDarkholme Así es como se lo explico a un recién llegado.
Cuando tu lo hagas

class A {
  static whatever(): B {
    return new B();
  }
}

el tipo de devolución de B significa que debe devolver una instancia de B . Si desea devolver la clase en sí, debe usar

class B {
 static whatever(): typeof B {
   return B;
 }
}

Ahora, al igual que en JavaScript, this en miembros estáticos se refiere a la clase. Entonces, si usa un tipo de devolución de this en un método estático, deberá devolver una instancia de la clase:

class A {
  static whatever(): this {
    return new this();
  }
}

si desea devolver la clase en sí, debe usar typeof this :

class A {
  static whatever(): typeof this {
    return this;
  }
}

así es exactamente como funcionan los tipos en TS desde siempre. Si escribe con una clase, TS espera una instancia. Si desea escribir para el tipo de clase en sí, debe usar typeof .

Entonces preguntaría por qué this no tiene el mismo significado en el cuerpo del método. Si this en la anotación de tipo del método de instancia tiene el mismo significado que this en el cuerpo del método correspondiente, ¿por qué no es lo mismo para el método estático?

Nos encontramos ante un dilema y hay tres opciones:

  1. make this significa diferentes cosas en diferentes contextos (confusión)
  2. introduzca una nueva palabra clave como self o static
  3. deje que el programador escriba más anotaciones de tipo extraño (afectar a algunos usuarios)

Prefiero el tercer enfoque porque refleja la semántica de tiempo de ejecución de la definición de método estático .
También detecta errores en el sitio de uso, en lugar de definir el sitio, es decir, como en este comentario , el error se informa en B.create , no class B extends A . Use site error es más preciso en este caso. (Considere que declara un método estático que hace referencia a this y luego declara una subclase abstracta).

Y, lo que es más importante, no requiere una propuesta de idioma para la nueva función.

De hecho, la preferencia difiere de las personas. Pero al menos quiero ver una propuesta más detallada como esta . Hay muchos problemas por resolver, como la asignabilidad de clases abstractas, las firmas de constructores incompatibles con subclases y las estrategias de informe de errores.

@HerringtonDarkholme Porque en el caso de los métodos de instancia, el tiempo de ejecución this es una instancia de objeto y no una clase, por lo que no hay lugar para la confusión. Está claro que el tipo this es literalmente el tiempo de ejecución this , no hay otra opción. Mientras que en el caso estático se comporta como cualquier otra anotación de clase en cualquier lenguaje de programación orientado a objetos: anótelo con una clase y esperará una instancia de vuelta. Además, en el caso de TS, porque en JavaScript las clases también son objetos, escríbalo con typeof una clase y recuperará la clase literal.

Quiero decir que probablemente hubiera sido más consistente cuando implementaron el tipo de devolución this para pensar también en el caso estático y en su lugar requerir que los usuarios escriban siempre typeof this para los métodos de instancia. Pero esa decisión se tomó entonces, así que tenemos que trabajar con ella ahora, y como dije, en realidad no deja lugar a confusión porque this en los métodos de instancia no produce ningún otro tipo (a diferencia de las clases, que tiene un tipo estático y un tipo de instancia), por lo que es distinto en ese caso y no confundirá a nadie.

Bien, supongamos que nadie se confundirá con this . ¿Qué hay de la asignabilidad de clase abstracta?

La estática de los métodos es algo arbitraria. Cualquier función puede tener propiedades y métodos. Si también se puede llamar con new , elegimos llamar a estas propiedades y métodos static . Ahora, esta convención se ha consolidado firmemente en ECMAScript.

@HerringtonDarkholme , sin duda tiene razón en que el uso de this causaría confusión. Sin embargo, dado que no hay nada incorrecto en el uso this , diría que está perfectamente bien, especialmente si se tiene en cuenta su astuto análisis de costo-beneficio de las diversas alternativas. Creo que this es la alternativa menos mala.

Solo traté de ponerme al día con este hilo de mi publicación original, siento que tal vez esto se ha desviado un poco.

Para aclarar, el deseo es que la anotación de tipo this sea ​​polimórfica cuando se usa en el constructor, específicamente como una plantilla/genérica. Con respecto a @shlomiassaf y @HerringtonDarkholme , creo que el ejemplo con métodos static puede ser confuso y no es la intención de este problema.

Aunque a menudo no se lo considera como tal, el constructor de una clase es estático. El ejemplo (que volveré a publicar con comentarios más aclaratorios) no declara this en un método estático, sino que declara this para uso futuro a través de un genérico en la anotación de tipo de un método estático .

La distinción es que no quiero que this se calcule inmediatamente en un método estático, sino en el futuro en uno dinámico.

// START LIBRARY CODE
// Constrains the constructor to one that creates things that extend from BaseModel
interface ModelConstructor<T extends BaseModel> {
    new(fac: ModelAPI<T>): T;
}

class ModelAPI<T extends BaseModel> {
    // skipping the use of a ModelConstructor in favor of typeof does not work
    // constructor(private modelType: typeof T) {}
    constructor(private modelType: ModelConstructor<T>) {}

    create() {
        return new this.modelType(this);
    }
}

class BaseModel {
    // This is where "polymorphic `this`" in static members matters. We are 
    // trying to say that the ModelAPI should create instances of whatever 
    // the *current* class is, not the BaseModel class. Much like it would 
    // at runtime.
    constructor(private fac: ModelAPI<this>) {}

    reload() {
        // `reload()` returns a new instance of type Any, incorrect
        return this.fac.create();
    }
}
// END LIBRARY CODE

// START APPLICATION CODE
// Create a custom model class with custom behavior
class Model extends BaseModel {}

// Create an instance of the model API that produces my custom type
let api = new ModelAPI<Model>(Model);  // ModalAPI should be able to infer "<Model>" from the constructor?
let modelInst = api.create();  // Returns type of Model, correct
let reset = modelInst.reload();  // Returns type of Any, incorrect
// END APPLICATION CODE

Para cualquiera que piense que esto es confuso, bueno, no es muy sencillo, estoy de acuerdo. Sin embargo, el uso de this en el constructor BaseModel no es realmente un uso estático, es un uso diferido que se calculará más adelante. Sin embargo, los métodos estáticos (incluido el constructor) no funcionan de esa manera.

Creo que en tiempo de ejecución todo esto funciona como se esperaba. Sin embargo, el sistema de tipos es incapaz de modelar este patrón en particular. La incapacidad de TypeScript para modelar un patrón de JavaScript es la base de por qué este problema está abierto.

Lo siento si este es un comentario confuso, escrito a toda prisa.

@xealot Entiendo su punto, pero otros problemas que sugirieron específicamente this polimórficos para métodos estáticos se cerraron como un duplicado de este. Supongo que la solución en TS habilitaría ambos casos de uso.

¿Cuál es su caso de uso exacto? Tal vez la solución 'esto' sea suficiente.

Lo uso en una biblioteca ORM personalizada con éxito
Le jeu. 20 de octubre 2016 a las 19:15, Tom Marius [email protected] a
escrito:

Tenga en cuenta que esto es bastante crítico ya que es un método de uso común
patrón en los ORM y una gran cantidad de código se comporta de manera muy diferente a la
el compilador y el sistema de tipos creen que debería.


Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-255169194 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAtAoRHc45B386xdNhuCLB8iW6i82e7Uks5q16GzgaJpZM4GsmOH
.

Gracias a @ceymard y @HerringtonDarkholme.

static create<T extends A>(this: {new (): T}) { return new this(); } hizo el truco para mí 👍

Es más oscuro que static create(): this { return new this(); } a primera vista, pero al menos es correcto. Captura con precisión el tipo de this dentro del método estático.

Espero que esto se pueda priorizar de alguna manera, aunque no sea el más votado o comentado, etc. Es muy molesto tener que usar el argumento de tipo para obtener el tipo correcto, por ejemplo:

let u = User.create<User>();

Cuando es prácticamente posible simplemente hacer:

let u = User.create();

Y es inevitable que static a veces se refiera a tipos de instancias.

Tuve otra idea con respecto a la discusión sobre si el tipo this en los miembros estáticos debería ser el tipo del lado estático de la clase o el lado de la instancia. this podría ser el lado estático, coincidiría con lo que se hace en los miembros de la instancia y cuál es el comportamiento del tiempo de ejecución, y luego podría obtener el tipo de instancia a través this.prototype :

abstract class Model {
  static findAll(): Promise<this.prototype[]>
}

class User extends Model {}

const users: User[] = await User.findAll();

esto estaría en línea con la forma en que funciona en JavaScript. this es el objeto de clase en sí, y la instancia está en this.prototype . Es esencialmente la misma mecánica que los tipos mapeados, equivalente a this['prototype'] .

De la misma manera, puede imaginar que el lado estático esté disponible en los miembros de la instancia a través this.constructor (no puedo pensar en un caso de uso para este cajero automático).

También quería mencionar que ninguna de las soluciones/trucos mencionados funciona para escribir el modelo ORM en Sequelize.

  • La anotación de tipo this no se puede usar en un constructor, por lo que no funciona para esta firma:
    ts abstract class Model { constructor(values: Partial<this.prototype>) }
  • Pasar el tipo como un argumento de clase genérico no funciona para métodos estáticos, ya que los miembros estáticos no pueden hacer referencia a parámetros genéricos.
  • Pasar el tipo como un argumento de método genérico funciona para métodos, pero no funciona para propiedades estáticas, como:
    ts abstract class Model { static attributes: { [K in keyof this.prototype]: { type: DataType, field: string, unique: boolean, primaryKey: boolean, autoIncrement: boolean } }; }

+1 por esta solicitud. mi ORM estará mucho más limpio.

Espero que veamos la solución limpia pronto.

Mientras tanto, ¡gracias a todos los que ofrecieron soluciones!

¿Podemos avanzar más en esto ya que no hay más discusión?

¿Dónde te gustaría llevarlo? Esta sigue siendo una forma en la que Typescript no puede modelar Javascript limpiamente hasta donde yo sé y no he visto otra solución que no sea simplemente no hacerlo.

Esta es una función imprescindible para los desarrolladores de ORM, ya que podemos ver que tres propietarios populares de ORM solicitaron esta función.

Las soluciones @pleerock @xealot se han propuesto anteriormente:

export type StaticThis<T> = { new (): T };

export class Base {
    static create<T extends Base>(this: StaticThis<T>) {
        const that = new this();
        return that;
    }
    baseMethod() { }
}

export class Derived extends Base {
    derivedMethod() { }
}

// works
Base.create().baseMethod();
Derived.create().baseMethod();
// works too
Derived.create().derivedMethod();
// does not work (normal)
Base.create().derivedMethod();

Estoy usando esto extensivamente. Las declaraciones de los métodos estáticos en las clases base son un poco pesadas, pero ese es el precio a pagar para evitar la distorsión del tipo de this dentro de los métodos estáticos.

@pleerock

Tengo un ORM interno que usa el patrón this: ampliamente sin problemas.

Creo que no hay necesidad de sobrecargar el idioma cuando la función ya está aquí, aunque es cierto que es un poco complicada. El caso de uso para una sintaxis más clara es bastante limitado en mi humilde opinión y puede introducir inconsistencias.

¿Tal vez podría haber un hilo de Stackoverflow con esta pregunta y soluciones como referencia?

@bjouhier @ceymard Expliqué por qué todas las soluciones en este hilo no funcionan para Sequelize: https://github.com/Microsoft/TypeScript/issues/5863#issuecomment -269463313

Y no se trata solo de ORM, sino también de la biblioteca estándar: https://github.com/Microsoft/TypeScript/issues/5863#issuecomment -244550725

@felixfbecker Acabo de publicar algo sobre el caso de uso del constructor y lo eliminé (me di cuenta de que TS no exige constructores en cada subclase justo después de la publicación).

@felixbecker Con respecto al caso del constructor, lo resuelvo apegándome a los constructores sin parámetros y proporcionando métodos estáticos especializados en su lugar (crear, clonar, ...). Más explícito y más fácil de escribir.

@bjouhier Eso significaría que tiene que volver a declarar básicamente todos los métodos en cada subclase de modelo. Mira cuántos hay en Sequelize: http://docs.sequelizejs.com/class/lib/model.js~Model.html

Mi argumento es desde una perspectiva completamente diferente. Si bien existen soluciones parciales y un tanto poco intuitivas y podemos discutir y estar en desacuerdo sobre si son suficientes o no, podemos estar de acuerdo en que esta es un área donde Typescript no puede modelar Javascript fácilmente.

Y tengo entendido que Typescript debería ser una extensión de Javascript.

@felixfbecker

Eso significaría que tiene que volver a declarar básicamente todos los métodos en cada subclase de modelo.

¿Por qué? Esa no es mi experiencia en absoluto. ¿Puede ilustrar el problema con un ejemplo de código?

@bjouhier Ilustré el problema en profundidad con ejemplos de código en mis comentarios en este hilo, para empezar https://github.com/Microsoft/TypeScript/issues/5863#issuecomment -222348054

Pero mira mi ejemplo de create anterior . El método create es un método estático. No se vuelve a declarar y, sin embargo, se escribe correctamente en la subclase. ¿Por qué entonces los métodos Model necesitarían una redefinición?

@felixfbecker Tu _para empezar_ ejemplo:

export type StaticThis<T> = { new (): T };

abstract class Model {
    public static tableName: string;
    public static findById<T extends Model>(this: StaticThis<T>, id: number): T {
        const instance = new this();
        // details omitted
        return instance;        
    }
}

class User extends Model {
    public static tableName = 'users';
    public username: string;
}

const user = User.findById(1); 
console.log(user.username);

@bjouhier Bien, parece que las anotaciones this: { new (): T } en realidad hacen que la inferencia de tipo funcione. Lo que me hace preguntarme por qué este no es el tipo predeterminado que usa el compilador.
La solución, por supuesto, no funciona para el constructor, ya que no puede tener un parámetro this .

Sí, no funciona para los constructores, pero puede solucionar el problema con un pequeño cambio de API, agregando un método estático create .

Lo entiendo, pero esta es una biblioteca de JavaScript, por lo que estamos hablando de escribir la API existente en una declaración.

Si this significara { new (): T } dentro de un método estático, entonces this no sería el tipo correcto para this dentro del método estático. Por ejemplo new this() no estaría permitido. Por consistencia, this debe ser el tipo de la función constructora, no el tipo de la instancia de la clase.

Tengo el problema de escribir una biblioteca existente. Si no tiene control sobre esa biblioteca, aún puede crear una clase base intermedia ( BaseModel extends Model ) con una función create correctamente escrita y derivar todos sus modelos de BaseModel .

Si desea acceder a las propiedades estáticas de la clase base, puede usar

public static findById<T extends Model>(this: (new () => T) & typeof Model, id: number): T {...}

Pero probablemente tenga un punto válido sobre el constructor. No veo una razón difícil por la que el compilador deba rechazar lo siguiente:

constructor(values: Partial<this>) {}

Estoy de acuerdo con @xealot en que aquí hay dolor. Sería genial si pudiéramos escribir

static findById(id: number): instanceof this { ... }

en vez de

static findById<T extends Model>(this: (new () => T), id: number): T { ... }

Pero necesitamos un nuevo operador en el sistema de escritura ( instanceof ?). Lo siguiente no es válido:

static findById(id: number): this { ... }

TS 2.4 rompió estas soluciones para mí:
https://travis-ci.org/types/sequelize/builds/247636686

@sandersn @felixfbecker Creo que este es un error válido. Sin embargo, no puedo reproducirlo de manera mínima. El parámetro de devolución de llamada es contravariante.

// Hooks
User.afterFind((users: User[], options: FindOptions) => {
  console.log('found');
});

¿Hay alguna posibilidad de que esto se solucione en algún momento?

Me encontré con esto hoy también. Básicamente, quería construir una clase base singleton, como esta:

abstract class Singleton<T> {
  private static _instance?: T

  public static function getInstance (): T {
    return this._instance || (this._instance = new T())
  }
}

El uso sería algo como:

class Foo extends Singleton<Foo> {
  bar () {
    console.log('baz!')
  }
}

Foo.getInstance().bar() // baz!

Probé alrededor de 10 variaciones de esto, con la variante StaticThis mencionada anteriormente, y muchas otras. Al final, solo hay una versión que incluso compilaría, pero luego el resultado de getInstance() se derivó como solo Object .

Siento que esto es mucho más difícil de lo que debe ser trabajar con construcciones como estas.

siguientes obras:

class Singleton {
    private static _instance?: Singleton;

    static getInstance<T extends Singleton>(this: { new(): T }) {
      const constr = this as any as typeof Singleton; // hack
      return (constr._instance || (constr._instance = new this())) as T;
    }
  }

  class Foo extends Singleton {
    foo () { console.log('foo!'); }
  }

  class Bar extends Singleton {
    bar () { console.log('bar!');}
  }

  Foo.getInstance().foo();
  Bar.getInstance().bar();

La parte this as any as typeof Singleton es fea pero indica que estamos engañando al sistema de tipos ya que _instance debe almacenarse en el constructor de la clase derivada.

Por lo general, la clase base estará enterrada en su marco, por lo que no hace mucho daño si su implementación es un poco fea. Lo que importa es el código limpio en las clases derivadas.

Esperaba en 2.8 nightlies poder hacer algo como esto:

static findById(id: number): InstanceType<this> { ... }

Lamentablemente no hubo tanta suerte :(

Los ejemplos de código de @bjouhier funcionan de maravilla. Pero rompa el autocompletado de IntelliSense.

Se lanzó React 16.3.0 y parece imposible escribir correctamente el nuevo método getDerivedStateFromProps , dado que un método estático no puede hacer referencia a los parámetros de tipo de clase:

(ejemplo simplificado)

class Component<P, S> {

    static getDerivedStateFromProps?<K extends keyof S>(nextProps: P, prevState: S): Pick<S, K> | null

    props: P
    state: S
}

¿Hay algún trabajo alrededor? (No cuento "escribir P y S como PP y SS y espero que los desarrolladores hagan que estas dos familias de tipos coincidan exactamente" como una sola :p)

Relaciones públicas: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24577

@cncolder También he sido testigo de la ruptura de intellisense. Creo que comenzó con TypeScript 2.7.

Una cosa que no veo aquí, extendiendo el ejemplo de Singleton: sería un patrón común hacer que una clase Singleton/Multiton tenga un constructor protegido, y esto no se puede extender si el tipo es { new(): T } como este impone un constructor público: el término this u otra solución provista aquí ( instanceof this , typeof this etc.) debería resolver ese problema. Esto es definitivamente posible arrojando un error en el constructor si el singleton no solicitó su creación, etc. pero eso anula el propósito de escribir errores: tener una solución de tiempo de ejecución ...

class Singleton {

  private static _instance?: Singleton;

  public static getInstance<T extends Singleton> ( this: { new(): T } ): T {
    const ctor: typeof Singleton = this as any; // hack
    return (ctor._instance || (ctor._instance = new this())) as T;
  }

  protected constructor ( ) { return; } 
}

class A extends Singleton {
  protected constructor ( ) {
    super();
  }
}

A.getInstance() // fails because constructor is not public

No sé por qué necesitaría un singleton en TS con getInstance() , simplemente puede definir un objeto literal. O si absolutamente desea usar clases (para miembros privados), exporte solo la instancia de la clase y no la clase, o use una expresión de clase ( export new class ... ).

Esto es más útil para Multiton, y creo que he encontrado un patrón que funciona... aunque todavía es una sensación muy extraña sin este tipo... en cambio, esto es necesario.

this -> { new(): T } & typeof Multiton | Function

class Multiton {
  private static _instances?: { [key: string]: any };

  public static getInstance<T extends Multiton> (
    this: { new(): T } & typeof Multiton | Function, key: string
  ): T {
    const instances: { [key: string]: T } =
      (this as typeof Multiton)._instances ||
     ((this as typeof Multiton)._instances = { });

    return (instances[key] ||
      (instances[key] = new (this as typeof Multiton)() as T)
    );
  }

  protected constructor ( ) { return; }
}

class A extends Multiton {
  public getA ( ): void { return; }
}
A.getInstance("some-key").getA();
assert(A.getInstance("some-key") === A.getInstance("some-key"))
new A(); // type error, protected constructor

También tengo un código para almacenar la clave para que sea accesible en la clase secundaria en la creación, pero lo eliminé por simplicidad...

Hola. Me encuentro con un problema que necesita "esto" polimórfico para el constructor. Similar a este comentario.

Quiero crear una clase. La clase en sí tiene muchas propiedades, por lo que el constructor no se verá bien si todas las propiedades de esa clase se inicializan a través de los parámetros del constructor.

Prefiero un constructor que acepte un objeto para inicializar propiedades. Por ejemplo:

class Vehicle {
  // many properties here
  // ...

  constructor(value: Partial<Vehicle>) {
      Object.assign(this, value)
  }
}

let vehicle = new Vehicle({
  // <-- IntelliSense works here
})

Entonces, quiero extender la clase. Por ejemplo:

class Car extends Vehicle {
  // more properties here
  // ...
}

let car = new Car({
  // <-- I can't infer Car's properties here
})

Sería muy bueno si el tipo this también se puede usar en el constructor.

class Vehicle {
  // ...
  constructor(value: Partial<this>) {
      // ...

Por lo tanto, IntelliSense puede inferir propiedades de la clase secundaria sin repeticiones adicionales: por ejemplo, redefiniendo el constructor. Se verá más natural que usar un método estático para inicializar la clase.

let car = new Car({
  // <-- Car's properties will be able to be inferred if Partial<this> is allowed
})

Muchas gracias por su consideración.

Me alegro de haber encontrado este hilo. Honestamente, creo que este es un problema muy serio y me desanimó mucho ver que se llevara a cabo en 3.0.

Mirando todas las soluciones y considerando las diversas soluciones alternativas con las que jugué durante los últimos años, llegué a la conclusión de que cualquier solución rápida que intentemos simplemente fallará si se implementa una solución. Y hasta entonces, cada coerción que hacemos simplemente requiere demasiado trabajo manual para mantenerla durante todo el proyecto. Y una vez publicado, no puede pedirles a los consumidores que comprendan su propia mentalidad y por qué eligió arreglar algo que es TypeScript si simplemente consumen su paquete en Vanilla pero, como la mayoría de nosotros, trabajan con VSCode.

Al mismo tiempo, escribir código para hacer feliz al sistema de tipos cuando el lenguaje real no es el problema va en contra de la misma razón por la que, para empezar, se usan las características de Typed.

Mi recomendación para todos es aceptar que los garabatos están defectuosos y usar //@ts-ignore because my code works as expected cuando sea posible.

Si su linter es inmune a la inteligencia humana razonable, es hora de encontrar uno que conozca su lugar.

ACTUALIZAR :
Quería agregar mi propio intento de aumentar la inferencia estática tal vez de manera segura . Este enfoque es estrictamente ambiental, ya que pretende estar fuera del camino. Es exhaustivamente explícito, lo que lo obliga a verificar sus aumentos y no solo asumir que la herencia hará el trabajo por usted.

export class Sequence<T> {
  static from(...values) {
    // … returns Sequence<T>
  };
}

export class Peekable<T> extends Sequence<T> {
  // no augmentations needed in actual class body
}

/// AMBIENT /// Usually keep those at the bottom of my files

export declare namespace Peekable {
  export function from<T>(... values: T[]): Peekable<T>;
}

Obviamente, no puedo garantizar que este patrón se mantenga, ni que satisfaga todos los escenarios en este hilo, pero por ahora, funciona como se esperaba.

¡Estas elecciones se derivan de la preocupante constatación de que, hasta este punto, los propios archivos lib de TypeScript solo incluyen dos declaraciones de clase!

matriz segura

/**
 * Represents an Automation SAFEARRAY
 */
declare class SafeArray<T = any> {
    private constructor();
    private SafeArray_typekey: SafeArray<T>;
}

VarDate

/**
 * Automation date (VT_DATE)
 */
declare class VarDate {
    private constructor();
    private VarDate_typekey: VarDate;
}

Discutido por un tiempo hoy. Puntos clave:

  • ¿ this se refiere al lado de la instancia o al lado estático?

    • Definitivamente el lado estático. Se puede hacer referencia al lado de la instancia a través de InstanceTypeOf pero lo contrario no es cierto. Esto también mantiene la simetría de que this en la firma tiene el mismo tipo que this en el cuerpo.

  • Problemas de solidez

    • No hay garantías de que una lista de parámetros de constructor de clase derivada tenga alguna relación con su lista de parámetros de constructor de clase base

    • Las clases rompen la sustituibilidad del lado estático en este sentido muy a menudo

    • Se impone la sustitución del lado no constructivo y esto es lo que suele interesar a la gente de todos modos.

    • Ya permitimos invocaciones incorrectas de new this() en métodos static

    • ¿Es probable que a nadie le importe el lado de la firma de construcción de this estáticos desde el exterior?

Soluciones existentes:

class Foo {
    static boo<T extends typeof Foo>(this: T): InstanceType<T> {
        return (new this()) as InstanceType<T>;
    }
}

class Bar extends Foo {
}

// b: Bar
let b = Bar.boo();

En particular, esto solo funciona si la firma de construcción Bar es correctamente sustituible por Foo

@RyanCavanaugh He estado tratando de encontrar una explicación de dónde proviene el valor de this en su ejemplo static boo<T extends typeof Foo>(this: T): InstanceType<T> , pero simplemente no lo entiendo. Para estar seguro, volví a leer todo sobre los genéricos, pero aún así, no. Si reemplazo this con clazz por ejemplo, ya no funcionará y el compilador se queja con "Se esperaban 1 argumentos, pero obtuvo 0". Así que algo de magia está pasando allí. ¿Podrías explicar? ¿O señalarme la dirección correcta en la documentación?

Editar:
Además, ¿por qué es extends typeof Foo y no simplemente extends Foo ?

@creynders this es un parámetro falso especial que se puede escribir para el contexto de la función. documentos

Usar InstanceType<T> no funciona cuando hay genéricos involucrados.

Mi caso de uso se parece a esto:

const _data = Symbol('data');

class ModelBase<T> {
    [_data]: Readonly<T>;

    protected constructor(data: T) {
        this[_data] = Object.freeze(data);
    }


    static create<T, V extends typeof ModelBase>(this: V, data : T): InstanceType<V> {
        return new this(data);
    }
}

interface IUserData {
    id: number;
}

class User extends ModelBase<IUserData> {}

User.create({ id: 5 });

Enlace del patio de recreo de TypeScript

@mastermatt oh wow, totalmente NO capté eso al leer los documentos... Gracias

Mirando el ejemplo de @RyanCavanaugh , pude hacer que nuestros métodos estáticos funcionaran, pero también tengo métodos de instancia que se refieren a los métodos estáticos, pero no puedo entender cómo escribir correctamente para eso.

Ejemplo:

class Foo {
    static boo<T extends typeof Foo>(this: T): InstanceType<T> {
        return (new this()) as InstanceType<T>;
    }

    boo<T extends typeof Foo>(this: InstanceType<T>): InstanceType<T> {
      return (this.constructor as T).boo();
    }
}

class Bar extends Foo {
}

// b: Bar
let b = Bar.boo();
// c: Foo
let c = b.boo();

Si pudiera encontrar una solución para este último problema, estaría listo hasta que surja algo oficial.

También es de destacar con ese ejemplo, si la subclase intenta anular cualquier método estático y luego llama a super , entonces también se enfadará... Aparte de estos dos problemas, la solución parece estar funcionando bien. Aunque, esos dos problemas bloquean bastante nuestro código.

IIRC esto se debe a que esto no se puede expresar en mecanografiado sin doblar un poco el sistema de tipos. Como en ; No se garantiza en absoluto que this.constructor sea lo que crees que es o algo así.

En ese caso preciso, haría que el método de instancia boo() devolviera this y hiciera un poco de trampa interna, obligando al compilador a aceptar que sé lo que estoy haciendo.

Mi razonamiento general es que quiero que la API sea lo más simple posible para el resto del código, aunque a veces signifique hacer un poco de trampa.

Si alguien tiene algo más robusto, me encantaría.

class Foo {
  static boo<T extends typeof Foo>(this: T): InstanceType<T> {
      return (new this()) as InstanceType<T>;
  }

  boo(): this {
    // @ts-ignore : wow this is ugly 
    return (this.constructor).boo();
  }
}

class Bar extends Foo {
}

// b: Bar
let b = Bar.boo();
// c: Bar !
let c = b.boo();

También puede ir a la ruta de la interfaz y hacer algo así;

interface FooMaker<T> {
  new(...a: any[]): T
  boo(): T
}


class Foo {
  static boo<T extends typeof Foo>(this: T): InstanceType<T> {
      return (new this()) as InstanceType<T>;
  }

  boo(): this {
    return (this.constructor as FooMaker<this>).boo();
  }
}

class Bar extends Foo {
}

// b: Bar
let b = Bar.boo();
// c: Bar !
let c = b.boo();

Esto también es hacer trampa, pero ¿tal vez un poco más controlado? Realmente no puedo decir

Entonces, ¿el primer argumento this en el método estático "boo" se trata de manera especial, no como el argumento normal? ¿Algún enlace a documentos que describa esto?

class Foo {
    static boo<T extends typeof Foo>(this: T): InstanceType<T> {
        return (new this()) as InstanceType<T>;
    }
}

class Bar extends Foo {
}

// b: Bar
let b = Bar.boo();

Tengo el mismo (al menos similar) problema.
Estoy usando Vanilla Javascript con JSDoc (no TypeScript), por lo que no puedo usar/implementar las soluciones que giran en torno a los genéricos propuestos en este hilo, pero la raíz parece ser la misma.
He abierto este número: #28880

Este problema tiene literalmente 3 años ya.
¿Alguien encontró una solución adecuada que podría funcionar para mí?

Tres años

El trabajo de @RyanCavanaugh es excelente para métodos estáticos, pero ¿qué pasa con un descriptor de acceso estático?

class Factory<T> {
  get(): T { ... }
}

class Base {
  static factory<T extends Base>(this: Constructor<T>): Factory<T> {
    //
  }

  // what about a getter?
  static get factory<** no generics allowed for accessors **> ...
}

Tres años

Vale, eres medio decente restando fechas. ¿Pero puedes ofrecer una solución? ;)

@RyanCavanaugh Este es un problema cuando usamos this en los generadores de la siguiente manera:

class C {
  constructor(f: (this: this) => void) {
  }
}
new C(function* () {
  this;
  yield;
});

Cuando hacemos generadores, no podemos usar funciones de flecha para usar this . Así que ahora tenemos que declarar el tipo this explícitamente en las subclases. Un caso real es el siguiente:

      class Component extends Coroutine<void> implements El {
        constructor() {
          super(function* (this: Component) {
            while (true) {
              yield;
            }
          }, { size: Infinity });
        }
        private readonly dom = Shadow.section({
          style: HTML.style(`ul { width: 100px; }`),
          content: HTML.ul([
            HTML.li(`item`)
          ] as const),
        });
        public readonly element = this.dom.element;
        public get children() {
          return this.dom.children.content.children;
        }
        public set children(children) {
          this.dom.children.content.children = children;
        }
      }

https://github.com/falsandtru/typed-dom/blob/v0.0.134/test/integration/package.ts#L469

No tenemos que declarar el tipo this en las subclases si podemos hacerlo en las clases base. Sin embargo, this no se inicializa cuando el generador se llama sincrónicamente en la superclase. Evité este problema en el código, pero este es un truco sucio. Por lo tanto, este patrón puede no coincidir originalmente con los lenguajes de programación basados ​​en clases.

No es el más limpio, pero podría ser útil para acceder a la clase secundaria desde un método estático.

class Base {
    static foo<T extends typeof Base>() {
        let ctr = Object.create(this.prototype as InstanceType<T>).constructor;
        // ...
    }
}

class C extends Base {
}

C.foo();

Aunque no estoy seguro de si el problema al que me enfrento actualmente se debe a esto, pero después de un breve vistazo a este problema, parece que ese es probablemente el caso. Corrija si ese no es el caso.

Así que tengo las siguientes 2 clases relacionadas por herencia.

export class Target {
  public static create<T extends Target = Target>(that: Partial<T>): T {
    const obj: T = Object.create(this.prototype);
    this.mapObject(obj, that);
    return obj;
  }
  public static mapObject<T extends Target = Target>(obj: T, that: Partial<T>) {
    // works with "strictNullChecks": false
    obj.prop1 = that.prop1;
    obj.prop2 = that.prop2;
  }

  public prop1!: string;
  constructor(public prop2: string) {}
}

export class SubTarget extends Target {
  public subProp!: string;
}

A continuación, agrego un método SubTarget mapObject la clase SubTarget de la siguiente manera.

  public static mapObject(obj: SubTarget, that: Partial<SubTarget>) {
    super.mapObject(obj, that);
    obj.subProp = that.subProp;
  }

Aunque esperaba que esto funcionara, aparece el siguiente error.

Class static side 'typeof SubTarget' incorrectly extends base class static side 'typeof Target'.
  Types of property 'mapObject' are incompatible.
    Type '(obj: SubTarget, that: Partial<SubTarget>) => void' is not assignable to type '<T extends Target>(obj: T, that: Partial<T>) => void'.
      Types of parameters 'obj' and 'obj' are incompatible.
        Type 'T' is not assignable to type 'SubTarget'.
          Property 'subProp' is missing in type 'Target' but required in type 'SubTarget'.

¿Se genera este error debido a este problema? De lo contrario, una explicación sería realmente genial.

Enlace al parque infantil

Vine aquí mientras buscaba una solución para anotar métodos estáticos que devuelven nuevas instancias de la clase real a la que se llama.

Creo que la solución sugerida por @SMotaal (en combinación con new this() ) es la más limpia y tiene más sentido para mí. Permite expresar la intención deseada, no me obliga a especificar el tipo en cada llamada al método genérico y no agrega ninguna sobrecarga en el código final.

Pero en serio, ¿cómo es que esto todavía no es parte del mecanografiado central? Es un escenario bastante común.

@danielvy : creo que la desconexión entre la programación orientada a objetos y los prototipos es la brecha mental más grande que no logra hacer felices a todos. Creo que las suposiciones básicas sobre las clases hechas mucho antes de que las características de clase evolucionaran en la especificación no se alinearon, y romper con eso es una trampa para muchas características de TypeScript en funcionamiento, por lo que todos están "priorizando" y eso está bien, pero no para " tipos" en mi opinión. No tener una solución que sea mejor que la competencia es solo un problema si hay competencia, es por eso que todos los huevos en una canasta siempre son malos para todos. Estoy siendo pragmático y sincero aquí.

Esto no funciona:

export class Base {
  static getEntitySchema<T extends typeof Base>(
    this: T,
  ): InstanceType<T> {
  }
}

export class Extension extends Base {
  static member: string = '';
  static getEntitySchema<T extends typeof Extension>(
    this: T,
  ): InstanceType<T> {
  }
}

Type 'typeof Base' is missing the following properties from type 'typeof Extension ': member.

Pero, ¿no debería permitirse esto ya que la Extensión extiende la Base?

Tenga en cuenta que esto funciona:

export class Extension extends Base {
  static member: string = '';
  static getEntitySchema<T extends typeof Base>(
    this: T,
  ): InstanceType<T> {
  }
}

pero entonces no puedes usar this.member dentro de él.

Entonces, supongo por la publicación de @ntucker que la solución alternativa solo funciona en un nivel de profundidad a menos que la clase extendida coincida exactamente con la clase base.

¡Hola! ¿Qué pasa con los constructores protegidos ?

class A {
  static create<T extends A>(
    this: {new(): T}
  ) {
    return new this();
  }

  protected constructor() {}
}

class B extends A {} 

B.create(); // Error ts(2684)

Si el constructor es público , está bien, pero si no, causa un error:

The 'this' context of type 'typeof B' is not assignable to method's 'this' of type '(new () => B) & typeof A'.
  Type 'typeof B' is not assignable to type 'new () => B'.
    Cannot assign a 'protected' constructor type to a 'public' constructor type.

Zona de juegos - http://tiny.cc/r74c9y

¿Hay alguna esperanza para esto? Acabo de encontrarme con esta necesidad una vez más :F

@ntucker Duuuuudee, lo lograste!!! La realización clave para mí fue hacer que el método de creación estática sea genérico, declarar this param y luego hacer el molde InstanceType<U> al final.

Patio de juegos

Desde arriba ^

class Base<T> {
  public static create<U extends typeof Base>(
    this: U
  ) {
    return new this() as InstanceType<U>
  }
}
class Derived extends Base<Derived> {}
const d: Derived = Derived.create() // works 😄 

Esto satisface perfectamente mi caso de uso, y parece que a partir de TS 3.5.1 también satisface a la mayoría de las personas en este hilo (consulte el área de juegos para explorar el accesorio estático theAnswer , así como el tercer nivel de extensión)

Toma caliente: ¿Creo que esto funciona ahora y se puede cerrar?

@jonjaques Por cierto , nunca usas T. Además, esto no resuelve el problema que describí, que anula los métodos.

Siento que la solución mencionada ya se sugirió hace casi 4 años... Y, sin embargo, no es algo que resuelva el problema.

Abrí un stackoverflow para ver si había otras soluciones alternativas que alguien pudiera conocer y alguien tenía un buen resumen del problema que estoy experimentando:

"Los genéricos utilizados como parámetros de método, incluidos estos parámetros, parecen ser contravariantes, pero en realidad siempre desea que estos parámetros se traten como covariantes (o tal vez incluso bivariantes)".

Entonces parece que esto es realmente algo que está roto en TypeScript y debería arreglarse ('esto' debería ser covariante o bivariante).

EDITAR: Este comentario tiene una mejor manera.

No tengo mucho que agregar, pero funcionó bien para mí desde https://github.com/microsoft/TypeScript/issues/5863#issuecomment -437217433

class Foo {
    static create<T extends typeof Foo>(this: T): InstanceType<T> {
        return (new this()) as InstanceType<T>;
    }
}

class Bar extends Foo { }

// typeof b is Bar.
const b = Bar.create()

Espero que esto sea útil para cualquiera que no quiera navegar a través de este largo número.

Otro caso de uso son las funciones de miembro de guardia de tipo personalizado en clases que no son:

type Baz = {
    type: "baz"
}
type Bar = {
     type: "bar"
}
type Foo = (Baz|Bar)&{
    isBar: () => this is Bar 
}

La solución casi me lleva a donde necesito estar, pero tengo una llave más para tirar los engranajes. Tome el siguiente ejemplo artificial:

interface IAutomobileOptions {
  make: string
}

interface ITruckOptions extends IAutomobileOptions {
  bedLength: number
}

export class Automobile<O extends IAutomobileOptions> {
  constructor(public options: O) {}

  static create<T extends typeof Automobile, O extends IAutomobileOptions>(
    this: T,
    options: O
  ): InstanceType<T> {
    return new this(options) as InstanceType<T>
  }
}

export class Truck<O extends ITruckOptions> extends Automobile<O> {
  constructor(truckOptions: O) {
    super(truckOptions)
  }
}

const car = Automobile.create({ make: 'Audi' })
const truck = Truck.create({ make: 'Ford', bedLength: 7 }) // TS Error on Truck

Aquí está el error:

The 'this' context of type 'typeof Truck' is not assignable to method's 'this' of type 'typeof Automobile'.
  Types of parameters 'truckOptions' and 'options' are incompatible.
    Type 'O' is not assignable to type 'ITruckOptions'.
      Property 'bedLength' is missing in type 'IAutomobileOptions' but required in type 'ITruckOptions'.ts(2684)

Esto tiene sentido para mí, ya que el método $# create 2$#$ en la clase Automobile no hace referencia a ITruckOptions a O , pero me pregunto si hay un solución para ese problema?

Por lo general, las opciones de mi clase de extensión para el constructor extenderán la interfaz de opciones de la clase base, por lo que sé que siempre incluirán los parámetros para la clase base, pero no tienen una forma confiable de garantizar que incluyan los parámetros para la extensión clase.

También tuve que recurrir a métodos anulados para extender las clases para informarles sobre los tipos de entrada y retorno esperados de la clase heredada, lo que simplemente huele un poco mal.

@ Jack-Barry esto funciona:

interface IAutomobileOptions {
  make: string
}

interface ITruckOptions extends IAutomobileOptions {
  bedLength: number
}

export class Automobile<O extends IAutomobileOptions> {
  constructor(public options: O) { }

  static create<T extends Automobile<O>, O extends IAutomobileOptions>(
    this: { new(options: O): T; },
    options: O
  ): T {
    return new this(options)
  }
}

export class Truck<O extends ITruckOptions> extends Automobile<O> {
  constructor(truckOptions: O) {
    super(truckOptions)
  }
}

const car = Automobile.create({ make: 'Audi' });
const truck = Truck.create({ make: 'Ford', bedLength: 7 });

Sin embargo, todavía no es ideal ya que los constructores son públicos, por lo que es posible hacer new Automobile({ make: "Audi" }) .

@elderapo Creo que eso me da lo que necesito: el ejemplo, por supuesto, es artificial, pero veo dónde me mordió un poco mi falta de comprensión de _Generics_. ¡Gracias por aclararme!

Espero que esto ayude a otros, avíseme si hay algún margen de mejora. No es perfecto, ya que las funciones de lectura podrían desincronizarse potencialmente con los tipos, pero es lo más cerca que pude estar del patrón que necesitábamos.

// Interface to ensure attributes that exist on all descendants
interface IBaseClassAttributes {
  foo: string
}

// Type to provide inferred instance of class
type ThisClass<
  Attributes extends IBaseClassAttributes,
  InstanceType extends BaseClass<Attributes>
> = {
  new (attributes: Attributes): InstanceType
}

// Constructor uses generic A to assign attributes on instances
class BaseClass<A extends IBaseClassAttributes = IBaseClassAttributes> {
  constructor(public attributes: A) {}

  // this returns an instance of type ThisClass
  public static create<A extends IBaseClassAttributes, T extends BaseClass<A>>(
    this: ThisClass<A, T>,
    attributes: A
  ): T {
    // Perform db creation actions here
    return new this(attributes)
  }

  // Note that read function is a place where typechecking could fail you if db
  //   return value does not match
  public static read<A extends IBaseClassAttributes>(id: string): A | null {
    // Perform db retrieval here assign to variable
    const dbReturnValue = {} as A | null
    return dbReturnValue
  }
}

interface IExtendingClassAttributes extends IBaseClassAttributes {
  bar: number
}

// Extend the BaseClass with the extending attributes interface
class ExtendingClass extends BaseClass<IExtendingClassAttributes> {}

// BaseClass
const bc: BaseClass = BaseClass.create({ foo: '' })
const bca: IBaseClassAttributes = BaseClass.read('a') as IBaseClassAttributes
console.log(bc.attributes.foo)
console.log(bca.foo)

// ExtendingClass
// Note that the create and read methods do not have to be overriden,
//  but typechecking still works as expected here
const ec: ExtendingClass = ExtendingClass.create({ foo: 'bar', bar: 0 })
const eca: IExtendingClassAttributes = ExtendingClass.read(
  'a'
) as IExtendingClassAttributes
console.log(ec.attributes.foo)
console.log(ec.attributes.bar)
console.log(eca.foo)
console.log(eca.bar)

Podría extender fácilmente este patrón a las acciones CRUD restantes.

En el ejemplo de @Jack-Barry, intento que la función read devuelva una instancia de la clase. Esperaba que funcionara lo siguiente:

class BaseClass<A extends IBaseClassAttributes = IBaseClassAttributes> {

  public static read<A extends IBaseClassAttributes, T extends BaseClass<A>>(
    this: ThisClass<A, T>,
    id: string,
  ): T | null {
    // Perform db retrieval here assign to variable
    const dbReturnValue = {} as A | null
    if (dbReturnValue === null) {
      return null;
    }
    return this.create(dbReturnValue);
  }
}

pero en su lugar aparece el error Property 'create' does not exist on type 'ThisClass<A, T>'. ¿Alguien tiene una solución para abordar esto?

@everhardt Dado que el método create es static , sería necesario invocarlo en el class de this , no en la instancia. Dicho esto, realmente no tengo una buena solución alternativa sobre cómo acceder dinámicamente a la función class que aún proporciona la verificación de tipo deseada.

Lo más cercano que puedo obtener no es particularmente elegante, y no puedo usar el type existente de BaseClass.create , por lo que fácilmente podrían perder la sincronización:

return (this.constructor as unknown as { create: (attributes: A) => T }).create(dbReturnValue)

No he _ probado esto.

@Jack-Barry entonces obtienes this.constructor.create is not a function .

Eso tiene sentido: Typescript interpreta this como la instancia, pero para el analizador eventual de javascript, this es la clase ya que la función es estática.

Tal vez no sea la extensión más inspirada del ejemplo de Jack-Barry, pero en este Playground Link puedes ver cómo lo resolví.

Lo escencial:

  • el tipo ThisClass debe describir todas las propiedades y métodos estáticos que los métodos de BaseClass (tanto estáticos como en la instancia) quieren usar con un 'esto' polimórfico.
  • cuando use una propiedad o método estático en un método de instancia, use (this.constructor as ThisClass<A, this>)

Es un trabajo doble, ya que debe definir los tipos de métodos estáticos y propiedades tanto en la clase como en el tipo ThisClass , pero para mí funciona.

editar: arreglado el enlace del patio de recreo

PHP solucionó este problema hace mucho tiempo. uno mismo. estático. esta.

Hola a todos, estamos en 2020 y tenemos _este_ problema. 😛

class Animal { 
  static create() { 
    return new this()
  }
}
class Bunny extends Animal {}
const bugs = Bunny.create() // const bugs: Animal

Esperaría que bugs sea ​​una instancia de Bunny .

¿Cuál es la solución actual? He intentado todo en este problema, nada parece solucionarse.

Actualización : esto funcionó, se perdió la definición del argumento this .

class Animal { 
  static create<T extends typeof Animal>(this: T): InstanceType<T> { 
    return (new this()) as InstanceType<T>
  }
}
class Bunny extends Animal {}
const bugs = Bunny.create() // const bugs: Bunny

El problema con eso es que los getters y setters no pueden tener parámetros de tipo.

También es demasiado detallado.

Estoy lidiando con un problema similar. Para implementar una "clase interna polimórfica", lo mejor que pude encontrar es lo siguiente:

class BaseClass {
  static InnerClass = class BaseInnerClass {};

  static createInnerClass<T extends typeof BaseClass>(this: T) {
    return new this.InnerClass() as InstanceType<T['InnerClass']>;
  }
}

class SubClass extends BaseClass {
  static InnerClass = class SubInnerClass extends BaseClass.InnerClass {};
}

const baseInnerClass = BaseClass.createInnerClass(); // => BaseInnerClass

const subInnerClass = SubClass.createInnerClass(); // => SubInnerClass

Parece funcionar bien, pero escribir createInnerClass() es demasiado detallado en mi opinión, y tendré muchos de ellos en mi base de código. ¿Alguien tiene alguna idea sobre cómo simplificar este código?

El problema con ese enfoque es que no funciona con getters y setters estáticos.

¿Qué tal implementar self. para propiedades estáticas? Tuve este problema hace 4 años y vuelvo al mismo hilo con el mismo problema.

@sudomaxime Eso está fuera del alcance de este problema y va en contra de los objetivos de diseño de TypeScript siempre que ECMAScript no admita de forma nativa self. como un alias para this.constructor. en las clases, lo cual es poco probable sucede dado que self es un nombre de variable común.

La solución de @abdullah-kasim parece funcionar como se describe, pero no puedo hacer que funcione con clases genéricas:

class Animal<A> {
  public thing?: A;

  static create<T extends typeof Animal>(this: T): InstanceType<T> {
    return new this() as InstanceType<T>;
  }
}

type Foo = {};
class Bunny extends Animal<Foo> {}

const bunny = Bunny.create(); // typeof bunny is Animal<unknown>
bunny.thing;                  // unknown :(

   __     __
  /_/|   |\_\  
   |U|___|U|
   |       |
   | ,   , |
  (  = Y =  )
   |   `  |
  /|       |\
  \| |   | |/
 (_|_|___|_|_)
   '"'   '"'

Agradecería cualquier idea sobre si esto es factible, ya que lo he estado jugando un poco sin suerte.

@stevehanson ¿Funcionaría esto para usted?

class Animal<A> {
  public thing?: A;

  static create<T>(this: new () => T): T {
    return new this() as T;
  }
}

type Foo = {asd: 123};
class Bunny extends Animal<Foo> {
    public hiya: string = "hi there"
}

const bunny = Bunny.create()


bunny.thing

const test = bunny.thing?.asd

const hiya = bunny.hiya

Probé esto en el patio de juegos mecanografiado.

Inspirado en este enlace:
https://selleo.com/til/posts/gll9bsvjcj-generic-with-class-as-argument-and-returning-instance

Y no estoy seguro de cómo logró implicar que T es la clase en sí . EDITAR: Ah, recordé, logró implicar T porque el parámetro silencioso this siempre pasará la clase en sí.

@abdullah-kasim ¡esto funcionó! ¡Wow gracias! Para mi caso específico, mi constructor toma un argumento, por lo que esto fue lo que me pareció, en caso de que ayude a alguien más:

  static define<C, T, F = any, I = any>(
    this: new (generator: GeneratorFn<T, F, I>) => C,
    generator: GeneratorFn<T, F, I>,
  ): C {
    return new this(generator);
  }

Algunas de las soluciones aquí no funcionan para captadores estáticos porque no puedo pasar ningún argumento en:

Dado el siguiente ejemplo, ¿cómo puedo mover el captador estático default a una clase principal?

class Letters {
  alpha: string = 'alpha'
  beta?: string
  gamma?: string
  static get default () {
    return new this()
  }
}

const x = Letters.default.alpha;

Esto puede ser algo que valga la pena considerar si alguna vez consideramos mejoras en la sintaxis.

Posiblemente relacionado: tengo dificultades para agregar otra capa de abstracción al ejemplo de @abdullah-kasim. Esencialmente, me gustaría poder convertir Animal en una interfaz y permitir que Bunny defina su propio create() estático.

Aquí hay un ejemplo práctico (¡perdónenme por abandonar la analogía del conejito!). Quiero poder definir algunos tipos de documentos diferentes, y cada uno debería poder definir un método de fábrica estático que convierta una cadena en una instancia de documento. Quiero hacer cumplir que un tipo de documento solo debe definir un analizador para sí mismo y no para otros tipos de documentos.

(los tipos en el ejemplo a continuación no son del todo correctos; espero que sea lo suficientemente claro lo que estoy tratando de lograr)

interface ParseableDoc {
    parse<T>(this: new () => T, serialized:string): T|null;
}

interface Doc {
    getMetadata():string;
}

// EXPECT: no error!
const MarkdownDoc:ParseableDoc = class implements Doc {
    constructor(private meta:string){ };
    getMetadata():string { return this.meta; };

    static parse(serialized:string):typeof MarkdownDoc | null {
        // do something specific
        return null;
    }
}

// EXPECT: type error, since class defines no static parse()
const MissingParseDoc:ParseableDoc = class implements Doc {
    constructor(private meta:string){ };
    getMetadata():string { return this.meta; };
}

// EXPECT: type error, since parse() should return a MismatchedDoc
const MismatchDoc: ParseableDoc = class implements Doc {
    constructor(private meta:string){ };
    getMetadata():string { return this.meta; };

    static parse(serialized:string):typeof MismatchDoc | null {
        // do something specific
        return null;
    }
}

(Creo) Me gustaría poder escribir algo como esto:

interface ParseableDoc {
    getMetadata():string;
    static parse<T extends ParseableDoc>(this: new () => T, serialized:string): T|null;
}

class MarkdownDoc implements ParseableDoc {
    getMetadata():string { return ""; }

    static parse(serialized:string):MarkdownDoc|null {
        // do something specific
        return null;
    }
}

¿Alguna idea de si también es posible una solución aquí?

EDITAR: Posible solución

Otro caso de uso en StackOverflow
Para diseñar una clase de tabla de búsqueda genérica.

// Generic part
abstract class Table<T extends Model> {
    instances: Map<number, T> = new Map();
}
abstract class Model {
    constructor(
        public readonly id: number,
        public table: Table<this>  // Error
    ) { 
        table.instances.set(id, this);
    }
}

// Example: a table of Person objects
class Person extends Model {
    constructor(
        id: number,
        table: Table<this>,  // Error
        public name: string
    ) {
        super(id, table);
    }
}
class PersonTable extends Table<Person> {}

const personTable = new PersonTable();
const person = new Person(0, personTable, 'John Doe');

// Note: the idea of using `this` as generic type argument is to guarantee
// that other models cannot be added to a table of persons, e.g. this would fail:
//     class SomeModel extends Model { prop = 0; }
//     const someModel = new SomeModel(1, person.table);
//                                        ^^^^^^^^^^^^

@Think7 comentó el 29 de mayo de 2016
😰 No puedo creer que esto todavía no esté arreglado/agregado...

jaja
¡2020 está aquí!

Esto es ridículo y demente. Es 2020. 4 años después, ¿por qué no se ha solucionado esto?

¿Por qué no implementar algo como

class Model {
  static create(){
     return new static()
  }
  //or
  static create(): this {
     return new this()
  }
}

class User extends Model {
  //...
}

let user = new User.create() // type === User
¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

Antony-Jones picture Antony-Jones  ·  3Comentarios

uber5001 picture uber5001  ·  3Comentarios

blendsdk picture blendsdk  ·  3Comentarios

wmaurer picture wmaurer  ·  3Comentarios

weswigham picture weswigham  ·  3Comentarios