interface Child extends Parent crea una interfaz nueva con todos los miembros de Parent más los suyos. Los valores del tipo hijo se pueden usar en cualquier sitio donde se espere el padre, porque tienen todas sus propiedades.
La misma palabra clave aparece en cuatro sitios de TypeScript, con significados relacionados pero distintos:
| Dónde | Ejemplo | Significado |
|---|---|---|
| Interfaz | interface Dog extends Animal {} | heredar los miembros del padre |
| Clase | class Dog extends Animal {} | heredar campos e implementaciones de métodos (JavaScript en tiempo de ejecución) |
| Parámetro genérico | <T extends Animal> | restricción: el argumento de tipo debe poder asignarse a Animal |
| Conditional type | T extends string ? A : B | prueba: ¿T se puede asignar a string? |
Extender varias interfaces
Enumera varios padres después de extends, separados por comas. El resultado tiene los miembros de todos ellos:
interface Identified {
id: number;
}
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Post extends Identified, Timestamped {
title: string;
}
// Post requires id, createdAt, updatedAt and title
Si dos padres declaran la misma propiedad con tipos que no son idénticos, la hija es el error TS2320 (Interface 'Post' cannot simultaneously extend types 'A' and 'B'), salvo que la hija vuelva a declarar esa propiedad con un tipo asignable a los dos.
Extender un alias de tipo
Un alias type no tiene cláusula extends. Se construye sobre él con una intersección, &, que combina los miembros de los dos lados. Una interfaz también puede extender directamente un alias de tipo objeto, y un alias de tipo puede intersecarse con una interfaz: los dos tipos se mezclan sin problema.
Las dos vías se comportan igual hasta que hay propiedades en conflicto: extends informa del conflicto en la declaración, mientras que & convierte la propiedad en never sin avisar. La comparación está en la página de interface frente a type, y los detalles de & en la página de tipos intersección.
Sobrescribir tipos de propiedades
Una interfaz hija puede volver a declarar una propiedad del padre, pero solo con un tipo que se pueda asignar al del padre. Estrechar string a un literal, o quitar null de una unión, está permitido. Cambiar el tipo a algo sin relación no:
index.ts(13,11): error TS2430: Interface 'Label' incorrectly extends interface 'Shape'.
Types of property 'kind' are incompatible.
Type 'number' is not assignable to type 'string'.
La regla existe para que un Label se pueda seguir usando en cualquier sitio donde se espera un Shape. Si kind pudiera ser un número, el código escrito para Shape que llama a shape.kind.toUpperCase() fallaría.
Sustituir propiedades con Omit
Cuando de verdad necesitas otro tipo para una propiedad heredada, quítala antes del padre con Omit y luego declara la nueva. Un caso habitual es convertir datos de una API, donde los ids y las fechas llegan como strings:
Omit<ApiUser, "id" | "createdAt"> es { name: string }, así que los nuevos id y createdAt no chocan con nada. Pick, Partial y los demás utility types se pueden extender de la misma manera.
extends con clases
class Dog extends Animal es la herencia de clases de JavaScript y, a diferencia del extends de las interfaces, existe en tiempo de ejecución: Dog recibe el constructor, los campos y las implementaciones de métodos de Animal, y new Dog(...) instanceof Animal es true. TypeScript añade comprobaciones de tipos encima, como la palabra clave override:
Una clase solo puede hacer extends de una clase, pero puede hacer implements de cualquier número de interfaces. implements no copia nada: solo comprueba que la clase declara lo que exige la interfaz. super, los miembros protegidos y override se explican en herencia.
extends en genéricos y conditional types
Dentro de los corchetes angulares, extends es una restricción. <T extends { length: number }> acepta cualquier argumento de tipo que tenga un length numérico, y permite que el cuerpo de la función use .length. En un conditional type, T extends U ? X : Y pregunta si T se puede asignar a U.
En los dos sitios extends significa «se puede asignar a», la misma relación que comprueba la forma de las interfaces. Las restricciones se explican a fondo en restricciones genéricas, y las pruebas a nivel de tipos en conditional types.
Preguntas frecuentes
¿Cómo se extiende una interfaz en TypeScript?
Escribe interface Child extends Parent { ... }. La hija tiene todas las propiedades del padre más las que declara ella. Para extender varias interfaces a la vez, sepáralas con comas: interface C extends A, B { ... }.
¿Cómo se extiende un alias de tipo en TypeScript?
Un alias de tipo no puede usar extends; combina los tipos con una intersección: type Admin = User & { permissions: string[] }. Una interfaz también puede extender directamente un alias de tipo objeto: interface Admin extends User { permissions: string[] }.
¿Puedo sobrescribir el tipo de una propiedad al extender una interfaz?
Solo con un tipo compatible y más estrecho: kind: "circle" puede sustituir a kind: string. Un tipo distinto, como number para una propiedad string, es el error TS2430 (Interface 'X' incorrectly extends interface 'Y'). Para sustituir una propiedad por un tipo sin relación, quítala primero: interface User extends Omit<ApiUser, "id"> { id: number }.
¿Qué diferencia hay entre extends e implements?
extends hereda: una interfaz recibe los miembros del padre, y una clase recibe los campos y las implementaciones de métodos de la clase padre. implements solo comprueba: una clase que implementa una interfaz debe declarar ella misma cada miembro, y no obtiene nada de la interfaz.
¿Qué significa extends en un genérico como <T extends string>?
Es una restricción, no herencia. T extends string significa que el argumento de tipo debe poder asignarse a string, así que dentro de la función puedes usar métodos de string sobre T. En un conditional type, T extends U ? X : Y es una prueba: si T se puede asignar a U, el resultado es X; si no, Y.