Typescript: No se pueden extender los tipos integrados

Creado en 14 nov. 2014  ·  14Comentarios  ·  Fuente: microsoft/TypeScript

El ES6 designa algunos objetos nativos como subclasificables, por ejemplo, Objeto, Booleano, Error, Mapa, Promesa, etc. La forma en que estas construcciones se modelan en lib.d.ts hace que sea imposible subclasificar, ya que se definen como un par de una var y una interfaz.

De la sección de especificaciones ES6 19.5.1:

El constructor Error está diseñado para ser subclasable. Puede usarse como el valor de una cláusula de extensión de una declaración de clase. Los constructores de subclase que pretenden heredar el comportamiento de Error especificado deben incluir una super llamada al constructor de Error para inicializar las instancias de subclase.

Actualmente, esto daría como resultado el error "Una clase solo puede extender otra clase":

class NotImplementedError extends Error {
    constructor() {
        super("Not Implemented");
    }
}
Bug ES6 Fixed

Comentario más útil

Incluso existe la posibilidad de extender la clase Error hoy en día, sigo teniendo inconvenientes con ella en el nodo. Por ejemplo, tengo problemas con "error.stack". Cuando hago throw new Error(...) obtengo error.stack , pero cuando hago throw new CustomError() - no lo hago. Para solucionarlo me obligué a usar este truco:

export class HttpError extends Error {
    httpCode: number;
    message: string;

    constructor(httpCode: number, message?: string) {
        super();
        if (httpCode)
            this.httpCode = httpCode;
        if (message)
            this.message = message;

        this.stack = new Error().stack;
    }
}

Todos 14 comentarios

Este es un problema general para cualquier biblioteca js que expone funciones que están destinadas a ser subclasificables. Si mi biblioteca exporta el constructor de Foo y espera recibir instancias de subclases de Foo (no solo objetos que implementan una interfaz similar a Foo), actualmente no hay forma de permitir eso.

Me gustaria poder decir

class Error;

No se emitiría javascript para tal declaración, solo una actualización de la tabla de símbolos de Typecript.

Para el caso general, me gustaría poder reemplazar el cuerpo de cualquier declaración de clase con un punto y coma, permitiendo cosas como

class Foo<X, Y> extends Bar<X> implements Baz<Y>;

lo que significaría que la biblioteca ya garantiza que Foo.prototype es una instancia de Bar.prototype e implementa la interfaz Baz.

@metaweta creo que puede hacer algunas redundancias:

interface Foo {
}
class Foo; // Foo is now a class!
interface Bar extends Foo {
}
class Bar extends Foo; // Two `extends Foo`s. 

interface X {
}
class X extends Foo; // Hmm?
interface Y extends Foo {
}
class Y; // Hmm?

Todavía tiene algunas posibilidades, ya que las interfaces subclasables funcionarán como clases abiertas. N.º 819

@metaweta, su primer caso debe ser manejado por una declaración de clase ambiental. p.ej

// myLib.d.ts
declare class Error {
}
// no code emitted here for this. 
// myfile.ts
// Error is subclassable,
class MyError extends Error {
}

Para el segundo caso, presentaría una sugerencia diferente.

Aquí está la lista completa de objetos "subclasables" de la especificación ES6. actualmente estos están todos definidos como pares interfaz / var.

  • Objeto
  • Booleano
  • Error
  • NativeError
  • Número
  • Fecha
  • Cuerda
  • RegExp
  • Formación
  • TypedArray (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array y DataView)
  • Mapa
  • Conjunto
  • WeakMap
  • Débil
  • ArrayBuffer
  • DataView
  • Promesa

Problemas:

  • Una vez que un tipo se define como una clase, no hay forma de extender el lado del miembro (extender el lado estático puede ocurrir a través de un módulo).
  • Las extensiones existentes para el lado de los miembros no funcionarán ya que no se puede redefinir una clase (cambio importante)
  • No hay forma de modelar llamadas a funciones en una clase, por ejemplo, Objeto, Cadena, Booleano, Fecha, Número, RegExp, Error y Matriz. (otra vez rompiendo el cambio)

Soluciones posibles:

  • Permitir extender cualquier tipo con una propiedad de prototipo y una firma de construcción
  • Permitir extender el lado de la instancia de una clase (por ejemplo, módulo Class.prototype {}, cortesía de @RyanCavanaugh )
  • Permitir definir firmas de llamadas en un constructor de clases

@mhegazy Gracias, no estaba al tanto de la construcción de declaración de clase ambiental.

Cuando extiendo la clase Error de esta manera (usando la definición de clase ambiental), lanzar ExtendedError no produce un seguimiento de pila ni asigna el mensaje correctamente.

declare class Error {
  constructor(message?: string);
}

class ExtendedError extends Error {
  constructor(message?: string) {
    super(message + " extended");
  }
}

//throw new Error("Test");
throw new ExtendedError("Test");

@unional, esto parece un problema de motor. según la especificación ES6 debería funcionar. Los motores deberían arreglarlos ahora que ES6 se ha ratificado.

Me las arreglé para que funcionara. En mi código js que estaba funcionando, tengo la llamada Error.captureStackTrace pero la elimino cuando la implemento en ts porque la declaración de Error no la tiene.

Hay un código de ejemplo:
Es una excepción similar a C # que toma una innerException.

declare class Error {
    public name:string;
    public message:string;
    public stack:string;
    constructor(message?:string);
    static captureStackTrace(error: Error, constructorOpt: any);
}

class Exception extends Error {
    public innerException: Error;
    constructor(message?: string, innerException?: Error|string) {
        // Guard against throw Exception(...) usage.
        if (!(this instanceof Exception)) return new Exception(message, innerException);
        super();
        if (typeof Error.captureStackTrace === 'function') {
            //noinspection JSUnresolvedFunction
            Error.captureStackTrace(this, arguments.callee);
        }
        this.name = "Exception";
        if (innerException) {
            if (innerException instanceof Error) {
                this.innerException = innerException;
                this.message = message + ", innerException: " + this.innerException.message;
            }
            else if (typeof innerException === "string") {
                this.innerException = new Error(innerException);
                this.message = message + ", innerException: " + this.innerException.message;
            }
            else {
                this.innerException = innerException;
                this.message = message + ", innerException: " + this.innerException;
            }
        }
        else {
            this.message = message;
        }
    }
}

Corregido por # 3516. Las clases ahora pueden extender expresiones arbitrarias de tipos de funciones constructoras.

¿Qué versión de TypeScript se enviará con esta corrección?
Revisé rápidamente las etiquetas de la versión 1.5.3 y 1.5.4, y parece que aún no se ha enviado, solo en la versión maestra por ahora.

EDITAR:
Lo siento, ahora mismo he notado que el error marcado por el hito "TypeScript 1.6"

¡Gracias por tu trabajo!

@kostrse , puede probar nuestras versiones nocturnas de TypeScript 1.6 ejecutando npm install -g typescript@next .

Hola,

Estoy usando TypeScript 1.6.2 y mi lib.es6.d.ts muestra Error (y Array, ...) como Interfaz, no como clase.
¿Esto ya está arreglado en 1.6.2?

¡Salud!

@jpsfs la solución, como señaló @ahejlsberg en https://github.com/Microsoft/TypeScript/issues/1168#issuecomment -112955503, es permitir que las clases extiendan expresiones arbitrarias de tipos de funciones de constructor.

Incluso existe la posibilidad de extender la clase Error hoy en día, sigo teniendo inconvenientes con ella en el nodo. Por ejemplo, tengo problemas con "error.stack". Cuando hago throw new Error(...) obtengo error.stack , pero cuando hago throw new CustomError() - no lo hago. Para solucionarlo me obligué a usar este truco:

export class HttpError extends Error {
    httpCode: number;
    message: string;

    constructor(httpCode: number, message?: string) {
        super();
        if (httpCode)
            this.httpCode = httpCode;
        if (message)
            this.message = message;

        this.stack = new Error().stack;
    }
}
¿Fue útil esta página
0 / 5 - 0 calificaciones