Menu

Herencia en TypeScript: extends, super y override

Herencia de clases en TypeScript: extends y super, sobrescribir métodos con tipos compatibles, la palabra clave override y noImplicitOverride, miembros protected, volver a declarar campos con declare y cuándo implements es la mejor herramienta.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Una clase de TypeScript hereda de otra con extends. La subclase obtiene todos los campos y métodos del padre, llama al constructor padre con super(...) y puede sobrescribir métodos, y TypeScript comprueba que cada sobrescritura siga siendo compatible con los tipos del padre.

Esto es la herencia de clases de JavaScript (consulta herencia en JavaScript para ver la cadena de prototipos que hay detrás). TypeScript añade las comprobaciones de tipos: un Dog se puede asignar en cualquier sitio donde se espere un Animal, y el compilador verifica la sobrescritura y la llamada a super.

Sobrescribir métodos

Un método de la subclase con el mismo nombre sustituye al del padre. Su tipo debe poder asignarse al del padre: parámetros iguales o compatibles, y un tipo de retorno igual o más estrecho. Si no, cualquier código que tenga un Animal podría llamar al método con argumentos que la subclase no sabe manejar.

Cambiar el parámetro a value: string sería el error TS2416: Property 'format' in type 'CurrencyFormatter' is not assignable to the same property in base type 'Formatter'. El bucle muestra por qué: llama a format con un número en cada elemento, sin saber qué subclase tiene.

Hay un agujero. Los parámetros de los métodos se comparan de forma bivariante, así que una sobrescritura que acepta un tipo más estrecho, como format(value: 1 | 2), compila aunque el bucle podría seguir pasándole 1234567. Los tipos de retorno se comprueban de forma estricta: devolver number en lugar de string es TS2416.

La palabra clave override y noImplicitOverride

override dice «este miembro sustituye a uno de la clase base», y el compilador te lo exige. Si la base no tiene ese miembro (una errata, o un método que alguien renombró en el padre), es un error:

index.ts(9,14): error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Animal'. Did you mean 'speak'?

Sin override, esa errata compila y speak() devuelve "..." sin avisar. La palabra clave solo ayuda donde se escribe, así que activa noImplicitOverride en tsconfig.json. Entonces cada método que sobrescribe un miembro de la base debe indicarlo, y un simple speak() en una subclase es el error TS4114 (This member must have an 'override' modifier because it overrides a member in the base class 'Animal').

{
    "compilerOptions": {
        "strict": true,
        "noImplicitOverride": true
    }
}

noImplicitOverride no forma parte de strict; tienes que añadirlo tú. override se borra del JavaScript compilado.

Miembros protected en las subclases

Los miembros private del padre son invisibles en la subclase. Los miembros protected son visibles, y esa es la razón de que exista protected: es la API que una clase padre ofrece a sus hijas.

Consulta modificadores de acceso para las reglas completas, incluido por qué ni private ni protected se aplican en tiempo de ejecución.

Volver a declarar un campo con un tipo más estrecho

A menudo una subclase sabe que un campo contiene algo más concreto de lo que dice el padre. Volver a declararlo como pet: Dog tiene un efecto en tiempo de ejecución con los campos de clase modernos: el campo de la subclase se inicializa a undefined después de que super() termine, borrando lo que guardó el constructor padre. Salvo que el constructor de la subclase lo vuelva a asignar, TypeScript lo informa como TS2612 (Property 'pet' will overwrite the base property in 'Home'). Usa declare para cambiar solo el tipo:

declare no genera nada, así que el valor asignado por el constructor de Home se conserva. El constructor que solo acepta un Dog es lo que hace cierto el tipo más estrecho.

implements frente a extends

Las dos palabras clave responden a preguntas distintas, y una clase puede usar ambas.

extendsimplements
Funciona conuna clasecualquier número de interfaces (o alias de tipos objeto)
Aporta códigosí: campos, métodos, constructorno, solo comprueba la forma
Existe en tiempo de ejecuciónsí, la cadena de prototiposno, se borra
instanceof funcionasíno
Uso típicocompartir comportamiento realprometer una forma al resto del código

Una clase solo puede extender una clase (TS1174: Classes can only extend a single class). Para combinar comportamiento de varios sitios, implementa varias interfaces y comparte código mediante composición (un campo que contiene un objeto auxiliar) o mixins.

Mixins

Un mixin es una función que recibe una clase y devuelve una subclase de ella. Ofrece una forma de herencia múltiple que pasa la comprobación de tipos:

El any[] de Constructor es obligatorio: TypeScript solo acepta como base de un mixin una clase cuyo constructor reciba un único parámetro rest de tipo any[]. Los mixins funcionan, pero hacen más difíciles de leer los tipos y las trazas de error, así que recurre primero a la composición.

Errores habituales

  • Llamar a un método sobrescribible desde el constructor base. El constructor base se ejecuta antes de que se inicialicen los campos de la subclase, así que una sobrescritura que lee this.items ve undefined, y una llamada como this.items.push(x) lanza un TypeError en tiempo de ejecución. TypeScript no lo detecta.
  • Olvidar super(...) o llamarlo tarde. Errores TS2377 y TS17009; el padre debe construir el objeto antes de que la subclase toque this.
  • Volver a declarar un campo del padre sin declare. El campo de la subclase lo restablece a undefined.
  • Jerarquías profundas. Cada nivel acopla la hija a los detalles internos del padre. Dos niveles suelen bastar; más allá, prefiere la composición o las interfaces.

Preguntas frecuentes

¿Cómo funciona la herencia en TypeScript?

Una clase hereda de otra con extends: class Dog extends Animal. La subclase obtiene los campos y métodos del padre, llama al constructor padre con super(...) y puede sobrescribir métodos. TypeScript comprueba que las sobrescrituras sigan siendo compatibles en tipos con el padre.

¿Qué hace la palabra clave override en TypeScript?

override marca un método o una propiedad que sustituye a uno de la clase base. Si la clase base no tiene ningún miembro con ese nombre, el compilador informa TS4117, lo que detecta erratas y miembros renombrados en el padre. Con noImplicitOverride activado, cada sobrescritura debe llevar la palabra clave (si no, TS4114).

¿Cómo llamo a un método de la clase padre en TypeScript?

Usa super.methodName(...) dentro del método de la subclase. En el constructor, super(...) llama al constructor padre y debe ejecutarse antes de cualquier uso de this.

¿TypeScript admite herencia múltiple?

No. Una clase solo puede extender una clase (TS1174: Classes can only extend a single class). Puede implementar cualquier número de interfaces, y los mixins (funciones que reciben una clase y devuelven una subclase) combinan comportamiento de varias fuentes.

¿Qué diferencia hay entre extends e implements?

extends hereda código real de una clase padre. implements solo comprueba que una clase encaja con la forma de una interfaz y no añade nada en tiempo de ejecución. Una clase puede hacer las dos cosas: class Dog extends Animal implements Pet.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR