Typescript: Не может расширять встроенные типы

Созданный на 14 нояб. 2014  ·  14Комментарии  ·  Источник: microsoft/TypeScript

ES6 определяет некоторые собственные объекты как подклассы, например, Object, Boolean, Error, Map, Promise и т. Д. Способ моделирования этих конструкций в lib.d.ts делает невозможным создание подкласса, поскольку они определены как пара из переменной и интерфейса.

Из раздела спецификации ES6 19.5.1:

Конструктор Error предназначен для создания подклассов. Он может использоваться как значение предложения extends объявления класса. Конструкторы подкласса, предназначенные для наследования указанного поведения при ошибке, должны включать супервызов конструктора Error для инициализации экземпляров подкласса.

В настоящее время это приведет к ошибке «Класс может расширять только другой класс»:

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

Самый полезный комментарий

Даже если сейчас есть возможность расширить класс Error меня все еще есть неудобства с ним на узле. Например, у меня проблемы с "error.stack". Когда я делаю throw new Error(...) я получаю error.stack , но когда я делаю throw new CustomError() - я этого не делаю. Чтобы решить эту проблему, я был вынужден использовать такой трюк:

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;
    }
}

Все 14 Комментарий

Это общая проблема для любой библиотеки js, которая предоставляет функции, которые предназначены для создания подклассов. Если моя библиотека экспортирует конструктор Foo и ожидает получения экземпляров подклассов Foo (а не только объектов, реализующих интерфейс, подобный Foo), в настоящее время нет способа разрешить это.

Я хотел бы иметь возможность сказать

class Error;

Для такого оператора не будет испускаться javascript, только обновление таблицы символов Typescript.

В общем случае я хотел бы иметь возможность заменять тело любого объявления класса точкой с запятой, допуская такие вещи, как

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

что означало бы, что библиотека уже гарантирует, что Foo.prototype является экземпляром Bar.prototype и реализует интерфейс Baz.

@metaweta Я думаю, это может сделать некоторые дублирования:

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?

У него все еще есть некоторые возможности, поскольку подклассифицируемые интерфейсы будут работать как классы с открытым концом. # 819

@metaweta ваш первый случай должен обрабатываться объявлением окружающего класса. например

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

Во втором случае я бы подал другой вопрос с предложением.

Вот полный список «подклассифицируемых» объектов из спецификации ES6. в настоящее время все они определены как пары интерфейс / переменная.

  • Объект
  • Булево
  • ошибка
  • NativeError
  • номер
  • Свидание
  • Строка
  • RegExp
  • Массив
  • TypedArray (Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array и DataView)
  • карта
  • Набор
  • WeakMap
  • WeakSet
  • ArrayBuffer
  • DataView
  • Обещание

Проблемы:

  • После того, как тип определен как класс, невозможно расширить членскую сторону (расширение статической стороны может происходить через модуль).
  • Существующие расширения на стороне участника не будут работать, поскольку вы не можете переопределить класс (критическое изменение)
  • Невозможно смоделировать вызовы функций для класса, например, Object, String, Boolean, Date, Number, RegExp, Error и Array. (снова ломка сдачи)

Возможные решения:

  • Разрешить расширение любого типа с помощью свойства прототипа и сигнатуры конструкции
  • Разрешить расширение экземпляра класса (например, модуль Class.prototype {}, любезно предоставлено @RyanCavanaugh )
  • Разрешить определение сигнатуры вызовов в конструкторе класса

@mhegazy Спасибо, я не знал о конструкции объявления окружающего класса.

Когда я расширяю класс Error таким образом (используя определение внешнего класса), выброс ExtendedError не приводит к трассировке стека и не назначает сообщение должным образом.

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 это похоже на проблему с двигателем. согласно спецификации ES6 он должен работать. Двигатели должны исправлять их сейчас, когда ES6 ратифицирован.

Мне удалось заставить его работать. В моем работающем js-коде у меня есть вызов Error.captureStackTrace, но я убираю его, когда реализую его в ts, потому что в объявлении Error его нет.

Вот пример кода:
Это исключение, подобное C #, которое принимает внутреннее исключение.

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;
        }
    }
}

Исправлено # 3516. Классы теперь могут расширять произвольные выражения типов функций конструктора.

В какой выпуск TypeScript будет выпущено это исправление?
Я быстро проверил лейблы версий 1.5.3 и 1.5.4, и кажется, что он еще не был отправлен, пока только в мастере.

РЕДАКТИРОВАТЬ:
Извините, прямо сейчас заметили, что ошибка отмечена вехой «TypeScript 1.6»

Спасибо за вашу работу!

@kostrse, вы можете попробовать наши тихие выпуски TypeScript 1.6, запустив npm install -g typescript@next .

Привет,

Я использую Typescript 1.6.2, и мой lib.es6.d.ts показывает ошибку (и массив, ...) как Интерфейс, а не класс.
Это уже исправлено в 1.6.2?

Ура!

@jpsfs исправление, как отмечает @ahejlsberg в https://github.com/Microsoft/TypeScript/issues/1168#issuecomment -112955503, должно позволить классам расширять произвольные выражения типов функций конструктора.

Даже если сейчас есть возможность расширить класс Error меня все еще есть неудобства с ним на узле. Например, у меня проблемы с "error.stack". Когда я делаю throw new Error(...) я получаю error.stack , но когда я делаю throw new CustomError() - я этого не делаю. Чтобы решить эту проблему, я был вынужден использовать такой трюк:

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;
    }
}
Была ли эта страница полезной?
0 / 5 - 0 рейтинги