Interfaz TypeScript versus tipo: Diferencias reales, probadas con el compilador
Las interfaces y los alias de tipos se superponen tanto que la elección a menudo parece arbitraria. Esto es lo que cada uno puede hacer y el otro no, confirmado por errores reales del compilador, además de una regla simple que su equipo puede seguir.
- Para formas de objetos simples, una interfaz y un alias de tipo son casi intercambiables.
- Sólo
typepuede nombrar uniones, tuplas y otros tipos que no sean objetos. - Solo
interfaceadmite la fusión de declaraciones, donde se combinan dos declaraciones. - Una regla común: interfaces para formas de objetos,
typepara todo lo demás, aplicadas de manera consistente.
Pocas preguntas sobre TypeScript causan tanto debate como Interfaz de TypeScript versus tipo. Muchos artículos enumeran diferencias que solían ser importantes en versiones anteriores pero que ahora están desactualizadas. Entonces, en lugar de repetir afirmaciones, las probamos.
Escribimos un archivo practicando cada diferencia y lo verificamos con TypeScript 5.6.3 en modo estricto. Los errores del compilador que se citan a continuación provienen de esa ejecución. El comportamiento puede cambiar entre versiones, así que compruébelo con la versión de TypeScript que utiliza su proyecto. Si eres nuevo en el lenguaje, comienza con nuestro tutorial de TypeScript para principiantes.
La respuesta corta
Un interface describe la forma de un objeto y se puede ampliar o reabrir. Un alias type da un nombre a cualquier tipo, incluidos objetos, uniones, tuplas y tipos de funciones. Cuando ambos pueden funcionar, la diferencia es principalmente el estilo.
donde son iguales
Estas dos declaraciones describen formas idénticas:
interface User {
id: number;
name: string;
email?: string;
}
type UserAlias = {
id: number;
name: string;
email?: string;
};
const a: User = { id: 1, name: "Asha" };
const b: UserAlias = a; // no error
TypeScript utiliza tipificación estructural, lo que significa que compara formas en lugar de nombres. Por eso se puede asignar un User a un UserAlias sin queja alguna. Ambos admiten propiedades opcionales, readonly propiedades, métodos y firmas de índice.
Interfaz versus tipo uno al lado del otro
| Característica | interfaz | tipo |
|---|---|---|
| Formas de objetos | si | si |
Sindicatos, como "a" | "b" |
No | si |
| Tuplas y primitivas | No | si |
| Tipos mapeados y condicionales | No | si |
| Declaración de fusión | si | No, es un error |
| Extendiendo | se extiende |
& intersección |
| Propiedades en conflicto | Error en la declaración | Puede convertirse silenciosamente en never |
Usado con implements |
si | Sí, si es un tipo de objeto. |
¿Qué solo el tipo puede hacer?
Muchos tipos útiles no son formas de objetos en absoluto, y sólo un alias de tipo puede nombrarlos:
type Status = "pending" | "paid" | "shipped"; // union
type Point = [number, number]; // tuple
type Handler = (event: string) => void; // function type
type Keys = keyof User; // computed type
type ReadonlyUser = Readonly<User>; // utility type
Los tipos de unión se encuentran entre las características más valiosas de TypeScript, porque el compilador rechaza cualquier valor fuera de la lista:
let s: Status = "paid";
s = "refunded";
// error TS2322: Type '"refunded"' is not assignable to type 'Status'.
Esto detecta errores tipográficos y valores obsoletos en el momento de la compilación, antes de que lleguen a producción.
Declaración de fusión
Si declara una interfaz con el mismo nombre dos veces, TypeScript las fusiona en una:
interface Settings {
theme: string;
}
interface Settings {
language: string;
}
const settings: Settings = { theme: "dark", language: "en" }; // no error
Intente lo mismo con un alias de tipo y el compilador se niega:
type Config = { theme: string };
type Config = { language: string };
// error TS2300: Duplicate identifier 'Config'.
La fusión es la forma en que las bibliotecas le permiten ampliar sus tipos. Por ejemplo, puede agregar una propiedad personalizada a la interfaz global Window o al objeto de solicitud de un marco, sin modificar la biblioteca. Eso es realmente útil.
La otra cara de la moneda es que la fusión puede ocurrir por accidente. Si dos archivos en un proyecto declaran una interfaz con el mismo nombre en el mismo ámbito, se combinan silenciosamente, lo que puede resultar confuso. Un alias de tipo hace que esa colisión sea un error.
se extiende vs intersecciones
Ambos le permiten construir sobre un tipo existente:
interface Admin extends User {
permissions: string[];
}
type AdminAlias = UserAlias & { permissions: string[] };
Para propiedades compatibles, el resultado es el mismo. La diferencia se manifiesta con un conflicto. Cuando una interfaz intenta cambiar el tipo de propiedad de manera incompatible, el compilador lo informa de inmediato:
interface Base { id: number }
interface Broken extends Base { id: string }
// error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
// Types of property 'id' are incompatible.
// Type 'string' is not assignable to type 'number'.
Una intersección no se queja cuando se declara. En cambio, la propiedad en conflicto se convierte en never, un tipo que ningún valor puede tener. El error solo aparece más tarde, cuando intentas usarlo:
type Mixed = { id: number } & { id: string };
const m: Mixed = { id: 1 };
// error TS2322: Type 'number' is not assignable to type 'never'.
Ese error posterior es más confuso, ya que apunta fuera del error real. Ésta es una razón práctica para preferir extends al crear tipos de objetos.
Clases e implementos.
Una clase puede prometer hacer coincidir una forma usando implements, y esto funciona con una interfaz o un alias de tipo de objeto:
class Account implements User {
constructor(public id: number, public name: string) {}
}
Esta compilado sin errores. Tenga en cuenta que implements solo verifica que la clase coincida. No agrega ninguna propiedad ni código. Una clase no puede implementar un tipo de unión, ya que no existe una forma única que coincida.
Tipos de utilidad y de solo lectura
Los tipos de utilidades como Readonly, Partial, Pick y Omit funcionan tanto con interfaces como con alias. El resultado es un nuevo tipo, que normalmente recibe un nombre con un alias de tipo:
const r: ReadonlyUser = { id: 3, name: "Dev" };
r.name = "Chitra";
// error TS2540: Cannot assign to 'name' because it is a read-only property.
Recuerde que readonly es solo una verificación en tiempo de compilación. En tiempo de ejecución, el objeto es JavaScript normal y aún se puede cambiar mediante código que omita los tipos.
Consejo: Los tipos desaparecen cuando TypeScript se compila en JavaScript. Los datos que llegan desde el exterior, como una respuesta API, no se verifican en tiempo de ejecución solo porque tienen un tipo. Valídelo, por ejemplo, con una biblioteca de esquemas. Nuestro tutorial de recuperación de API cubre el manejo seguro de respuestas de API.
Rendimiento del compilador
La propia guía de rendimiento del equipo de TypeScript sugiere preferir interfaces que usan extends en lugar de intersecciones grandes, porque el compilador puede almacenar en caché las relaciones de interfaz de manera más eficiente. En proyectos pequeños y medianos es poco probable que notes alguna diferencia. Puede ser importante en bases de código muy grandes con tipos profundamente compuestos. No lo medimos nosotros mismos.
¿Cuál deberías usar?
Una regla ampliamente utilizada, de acuerdo con las pautas del manual de TypeScript, es:
- Utilice
interfacepara formas de objetos, especialmente API públicas y cualquier cosa que implementen las clases u otras puedan extenderse. - Utilice
typepara uniones, tuplas, tipos de funciones y tipos asignados o calculados.
| tu estas definiendo | uso |
|---|---|
| Un modelo de datos, como Usuario o Pedido | interfaz |
| Un conjunto de valores permitidos. | type unión |
| Un par o triple de longitud fija | type tupla |
| Una firma de devolución de llamada | tipo |
| Ampliando los tipos de una biblioteca | interface, mediante la fusión |
| Una variación de un tipo existente. | type con un tipo de utilidad |
Algunos equipos simplemente usan type en todas partes y eso también funciona bien. Lo que más importa es la coherencia, así que elige una convención, escríbela en tu guía de estilo y deja que un linter la haga cumplir. La regla ESLint consistent-type-definitions de mecanografiado-eslint puede hacer esto.
Errores comunes
- Debate sin fin. Ambos están bien para los objetos, así que establece la regla una vez y sigue adelante.
- Fusión accidental. La reutilización de un nombre de interfaz en el mismo ámbito los combina silenciosamente.
- Intersecciones conflictivas. Crean
nevertipos con errores confusos más adelante. - Tipos de confianza en tiempo de ejecución. Se borran, así que valide los datos externos.
- Usando
anypara silenciar errores. Desactiva la comprobación. Prefieraunknowny limítelo.
Cambiar entre ellos en una base de código existente
Cambiar un interface por un type, o al revés, suele ser seguro para formas de objetos simples, porque TypeScript compara los tipos por su estructura, no por sus nombres. Aún es necesario comprobar algunas cosas antes de cambiar:
- La conversión de una interfaz a un alias de tipo elimina la fusión de declaraciones, por lo que cualquier segunda declaración con el mismo nombre se convierte en un error de identificador duplicado.
- La conversión de un alias de tipo en una interfaz solo funciona para formas de objetos. Las uniones, tuplas y tipos mapeados deben permanecer como alias de tipo.
- Los mensajes de error y los desplazamientos del editor pueden mostrar el tipo de forma diferente, lo que puede sorprender a los compañeros de equipo pero no cambia el comportamiento.
Realice el cambio en una solicitud de extracción enfocada, ejecute el compilador en modo estricto y su conjunto de pruebas, y evite mezclarlo con el trabajo de funciones para que los revisores puedan ver que nada más cambió.
Conclusión
La pregunta entre interfaz y tipo tiene una respuesta tranquila: se superponen para los objetos, cada uno tiene algunas habilidades únicas y la coherencia importa más que la elección. Utilice interfaces para formas y tipos para todo lo demás, a menos que su equipo prefiera lo contrario. Para ver las diferencias usted mismo, copie los ejemplos anteriores en un archivo y ejecute tsc --noEmit --strict en él.
Preguntas frecuentes
¿Cuál es la diferencia entre interfaz y tipo en TypeScript?
Ambos pueden describir formas de objetos. Solo el tipo puede nombrar uniones, tuplas y tipos calculados, mientras que solo la interfaz admite la fusión de declaraciones.
¿Debo usar una interfaz o un tipo para los accesorios de React?
Cualquiera de los dos funciona bien. Muchos equipos usan interfaces para accesorios, mientras que otros prefieren el tipografía para mantener la coherencia con las uniones. Elija uno y aplíquelo a lo largo del proyecto.
¿Puede un tipo extender una interfaz?
Sí, utilizando una intersección como tipo Admin = Usuario & {permisos: cadena[]}. Una interfaz también puede ampliar un alias de tipo de objeto.
¿La interfaz es más rápida que el tipo?
La guía de rendimiento de TypeScript favorece que la interfaz se extienda a grandes intersecciones en grandes bases de código. En proyectos típicos, es poco probable que la diferencia sea perceptible.
¿Qué es la fusión de declaraciones?
Es cuando dos interfaces con el mismo nombre en el mismo ámbito se combinan en una. Los alias de tipo no se pueden fusionar y los nombres duplicados provocan un error.
sigue aprendiendo
¿Necesitas ayuda con pruebas, WordPress o SEO?
Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.
Más guías sobre testing, IA y desarrollo moderno
¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.
Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas
Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…
Leer artículo →DevOpsExplicación de las variables de entorno: archivos .env, secretos y configuración bien hechos
Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…
Leer artículo →Desarrollo de softwareExplicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429
La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …
Leer artículo →Automatización de pruebasBurlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan
Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…
Leer artículo →Pruebas de softwareExplicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%
Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…
Leer artículo →Bases de datos y SQLDELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno
DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…
Leer artículo →Bases de datos y SQLVistas SQL explicadas: cree, use y evite los errores, con ejemplos
Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…
Leer artículo →Desarrollo de softwareInterfaz TypeScript versus tipo: diferencias reales, probadas con el compilador
Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…
Leer artículo →Desarrollo de softwarePython *args y **kwargs explicados: funciones flexibles con ejemplos claros
Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…
Leer artículo →Desarrollo de softwareFunciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas
Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…
Leer artículo →Inteligencia artificial¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar
¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…
Leer artículo →DevOpsMonitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos
Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…
Leer artículo →DevOpsDocker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta
Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…
Leer artículo →Automatización de pruebasTDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una
TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …
Leer artículo →Desarrollo de softwareGit merge vs rebase: diferencias, ejemplos y cuándo usar cada uno
Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.
Leer artículo →Desarrollo de softwareList comprehension en Python: sintaxis, ejemplos y cuándo evitarla
List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.
Leer artículo →