Typescript: Não é possível estender os tipos integrados

Criado em 14 nov. 2014  ·  14Comentários  ·  Fonte: microsoft/TypeScript

O ES6 designa alguns objetos nativos como subclassíveis, por exemplo, Object, Boolean, Error, Map, Promise ..etc. A forma como essas construções são modeladas em lib.d.ts torna impossível a subclasse, já que são definidas como um par de uma var e uma interface.

Da seção 19.5.1 de especificação ES6:

O construtor Error foi projetado para ser subclassível. Pode ser usado como o valor de uma cláusula extends de uma declaração de classe. Os construtores de subclasse que pretendiam herdar o comportamento de Error especificado devem incluir uma super chamada ao construtor de Error para inicializar instâncias de subclasse.

Atualmente, isso resultaria no erro "Uma classe só pode estender outra classe":

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

Comentários muito úteis

Mesmo que exista a capacidade de estender Error classe hoje em dia, ainda estou tendo inconvenientes com isso no nó. Por exemplo, estou tendo problemas com "error.stack". Quando eu faço throw new Error(...) eu ganho error.stack , mas quando eu faço throw new CustomError() - eu não. Para resolver, forcei a usar este truque:

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 comentários

Este é um problema geral para qualquer biblioteca js que expõe funções destinadas a serem subclassíveis. Se minha biblioteca exporta o construtor Foo e espera receber instâncias de subclasses de Foo (não apenas objetos que implementam uma interface semelhante a Foo), atualmente não há como permitir isso.

Eu gostaria de poder dizer

class Error;

Não haveria javascript emitido para tal instrução, apenas uma atualização da tabela de símbolos do Typescript.

Para o caso geral, gostaria de substituir o corpo de qualquer declaração de classe por um ponto e vírgula, permitindo coisas como

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

o que significaria que a biblioteca já garante que Foo.prototype é uma instância de Bar.prototype e implementa a interface do Baz.

@metaweta , acho que pode causar algumas redundâncias:

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?

Ele ainda tem alguns potenciais, pois as interfaces subclassíveis funcionarão como classes abertas. # 819

@metaweta, seu primeiro caso deve ser tratado por uma declaração de classe ambiente. por exemplo

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

Para o segundo caso, eu arquivaria um problema de sugestão diferente para ele.

Aqui está a lista completa de objetos "subclassíveis" da especificação ES6. atualmente, todos são definidos como pares de interface / var.

  • Objeto
  • boleano
  • Erro
  • NativeError
  • Número
  • Encontro
  • Corda
  • RegExp
  • Array
  • TypedArray (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array e DataView)
  • Mapa
  • Conjunto
  • WeakMap
  • WeakSet
  • ArrayBuffer
  • Exibição de dados
  • Promessa

Problemas:

  • Depois que um tipo é definido como uma classe, não há como estender o lado do membro (estender o lado estático pode acontecer por meio de um módulo).
  • As extensões existentes para o lado do membro não funcionarão porque você não pode redefinir uma classe (alteração de quebra)
  • Não há como modelar chamadas de função em uma classe, por exemplo, Object, String, Boolean, Date, Number, RegExp, Error e Array. (novamente quebrando a mudança)

Soluções possíveis:

  • Permite estender qualquer tipo com uma propriedade de protótipo e uma assinatura de construção
  • Permitir a extensão do lado da instância de uma classe (por exemplo, módulo Class.prototype {}, cortesia de @RyanCavanaugh )
  • Permitir a definição de assinaturas de chamada em um construtor de classe

@mhegazy Obrigado, não estava ciente da construção de declaração de classe ambiente.

Quando eu estendo a classe Error dessa maneira (usando a definição de classe ambiente), lançar o ExtendedError não produz rastreamento de pilha nem atribui mensagem corretamente.

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, parece um problema de motor. de acordo com a especificação ES6, deve funcionar. Os motores devem consertá-los agora que o ES6 foi ratificado.

Eu consegui fazer funcionar. No meu código js que estava funcionando, tenho a chamada Error.captureStackTrace, mas a retiro quando a implemento em ts porque a declaração Error não a contém.

Existe um código de exemplo:
É uma exceção semelhante ao C # que leva uma exceção 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;
        }
    }
}

Corrigido por # 3516. As classes agora podem estender expressões arbitrárias de tipos de função de construtor.

Qual versão do TypeScript esta correção será lançada?
Eu verifiquei rapidamente os rótulos de versão 1.5.3 e 1.5.4, e parece que ainda não foi enviado, apenas no master por enquanto.

EDITAR:
Desculpe, agora notei que o bug marcado pelo marco "TypeScript 1.6"

Obrigado pelo seu trabalho!

@kostrse você pode experimentar nossas novas versões do TypeScript 1.6 executando npm install -g typescript@next .

Oi,

Estou usando Typescript 1.6.2 e meu lib.es6.d.ts mostra Error (and Array, ...) como Interface, não classe.
Isso já foi corrigido no 1.6.2?

Felicidades!

@jpsfs a correção, conforme observado por @ahejlsberg em https://github.com/Microsoft/TypeScript/issues/1168#issuecomment -112955503, é permitir que as classes estendam expressões arbitrárias de tipos de função do construtor.

Mesmo que exista a capacidade de estender Error classe hoje em dia, ainda estou tendo inconvenientes com isso no nó. Por exemplo, estou tendo problemas com "error.stack". Quando eu faço throw new Error(...) eu ganho error.stack , mas quando eu faço throw new CustomError() - eu não. Para resolver, forcei a usar este truque:

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;
    }
}
Esta página foi útil?
0 / 5 - 0 avaliações