Búsqueda de sitios web

typescript - ¿Cuál es el tipo TS de una clase que extiende otra clase?


const Err: ErrorConstructor = class Err extends Error {};

ErrorConstructor no es correcto aquí, pero no estoy seguro de cómo definirlo correctamente. TypeScript no está siendo útil aquí y si no defino el tipo, simplemente dice que es "typeof Err"

Tenga en cuenta que esta es una versión muy condensada de lo que estoy enfrentando, sé que no tengo que definirlo en este ejemplo exacto, pero esta es una reproducción mínima.

Respuesta #1

Si entiendo correctamente lo que quieres hacer, ErrorConstructor es una plantilla que quieres que cada uno de tus derivados de Error siga.

En tal caso, yo derivaría una clase CustomError de Error, y luego derivaría las otras clases Err de CustomError, o implementaría una interfaz ICustomError además de extender directamente de Error.

Di que quieres que todos tus tipos Err den información sobre la recuperabilidad:

opción 1: dos niveles de herencia

class CustomError extends Error {
    constructor (
        public message: string,
        public recoverable: boolean
    ) {
        super();
        this.name = 'customError';
    }
}

class ErrA extends CustomError {

    constructor (
        public message: string,
        public recoverable: boolean
    ) {
        super(message, recoverable);
        this.name = 'ErrA';
    }
};

opción 2: extends + implements

interface ICustomError {
    recoverable: boolean;
}

class ErrB extends Error implements ICustomError {
    constructor (
        public message: string,
        public recoverable: boolean
    ) {
        super();
        this.name = 'ErrB';
    }
};

Ahora, ¿cuál es el tipo de errA y errB en lo siguiente? Es ErrA y ErrB, duh.

const errA = new ErrA('a', true);
const errB = new ErrB('b', true);

Pero dado que TS es un sistema de tipos estructural, realmente no te importa cómo fueron construidos y puedes usar cualquiera de las siguientes funciones para mostrar el mensaje de ambos errA y errB

function displayCustomError(error: CustomError) {
    return error.message;
}

function displayCustomError2(error: Error & ICustomError) {
    return error.message;
}

function displayError(error: Error) {
    return error.message;
}

Respuesta #2

Estás obteniendo typeof Err porque las clases son valores especiales en TypeScript. Al igual que los enums, pueden usarse como un tipo y como un valor.

Considera el siguiente ejemplo:

class A extends Error { }
class B extends Error { }

const a = (arg: A) => arg

a(new A()) // ok

a(A) // error

/////////////////////////////


const b = (arg: typeof A) => arg

b(new A()) // error

b(A) // ok

Si esperas que el argumento sea un tipo de instancia de clase, usa la función a.

Si esperas que el argumento sea un tipo de clase en sí mismo, usa la función b.

Si quieres crear MustExtendsSomeClass, creo que puedes simplemente usar typeof AnyClass:

class Animal {
    tag = 'animal'
}

class Dog extends Animal { }
class Cat extends Animal { }
class Ant { }

type MustExtendsAnimal = typeof Animal;

const e = <T extends MustExtendsAnimal>(error: T) => e

e(Dog) // ok
e(Cat) // ok
e(Ant) // error

Como habrás notado, Ant no se puede asignar y es un comportamiento esperado.

¿Pero por qué no funciona con la clase Error?

class A extends Error { }

type MustExtendsAnimal = typeof Error;

const e = <T extends MustExtendsAnimal>(error: T) => e

e(A) // error

Porque es una clase incorporada con una firma de tipo no estándar para clases:

interface Error {
    name: string;
    message: string;
    stack?: string;
}

interface ErrorConstructor {
    new(message?: string): Error;
    (message?: string): Error;
    readonly prototype: Error;
}

declare var Error: ErrorConstructor;

Se puede usar con la palabra clave new new Error('error personalizado') y como una función regular Error(123)

Estoy dispuesto a apostar que esta pregunta es relativa a nuestro problema. Si quieres representar un tipo de clase que extiende la clase Error, debes asegurarte de que llamarla sin new creará una instancia de la clase Error.

Algo así:

const Err = function (this: Error, message?: string) {
    if (new.target) {
        // If this function was called with new keyword
        this.message = message || ''
        this.name = 'some name'

        // some inheritance logic
    }
    // some inheritance logic
    return new Error('custom error')
}

Pero esta función tampoco funciona:


const Err = function (this: Error, message?: string) {
    if (new.target) {
        // If this function was called with new keyword
        this.message = message || ''
        this.name = 'some name'

        // some inheritance logic
    }
    // some inheritance logic
    return new Error('custom error')
}

type MustExtendsAnimal = typeof Error;

const e = <T extends MustExtendsAnimal>(error: T) => e

e(Err) // error

porque TS no reconoce esta función como un constructor.

Por eso no puedes crear ese tipo para la clase Error.

Creo que puedes usar esta solución:

type Overload<T extends InstanceType<typeof Error>> = ((this: Error, message?: string | undefined) => T) & { new(message?: string | undefined): T }

class A extends Error { }

const Err = A as Overload<A>

type MustExtendsAnimal = typeof Error;

const e = <T extends MustExtendsAnimal>(error: T) => e

e(Err) // error