TypeScript
Tutorial TypeScript: Unknown, never y tipos especiales
Aprende los tipos especiales unknown, never, void y las aserciones seguras en TypeScript para mejorar la seguridad y flexibilidad de tu código.
Aprende TypeScript y certifícateunknown vs any
En TypeScript, el sistema de tipos es una de sus características más potentes, permitiéndonos definir con precisión qué tipo de datos puede contener cada variable. Dentro de este sistema, existen dos tipos especiales que ofrecen flexibilidad cuando no conocemos el tipo exacto de un valor: unknown
y any
.
Aunque a primera vista pueden parecer similares, representan enfoques fundamentalmente diferentes para manejar tipos desconocidos.
El tipo any: flexibilidad sin restricciones
El tipo any
es el más permisivo en TypeScript, permitiendo asignar cualquier valor a una variable y realizar cualquier operación con ella sin restricciones de tipo:
let valor: any = 10;
valor = "hola";
valor = true;
valor = { nombre: "TypeScript" };
// Con any podemos hacer cualquier operación sin errores de compilación
valor.toUpperCase();
valor.inexistente();
valor();
Este comportamiento hace que any
sea extremadamente flexible, pero también elimina prácticamente todas las ventajas del sistema de tipos de TypeScript. Cuando usamos any
, le estamos diciendo al compilador: "confía en mí, sé lo que estoy haciendo", pero perdemos la seguridad de tipos.
El tipo unknown: seguridad con flexibilidad
Introducido en TypeScript 3.0, unknown
representa un tipo desconocido pero de forma segura. Podemos asignar cualquier valor a una variable de tipo unknown
, pero no podemos realizar operaciones con ella sin antes verificar su tipo:
let valor: unknown = 10;
valor = "hola";
valor = true;
valor = { nombre: "TypeScript" };
// Estas operaciones generarán errores de compilación
// valor.toUpperCase(); // Error: Object is of type 'unknown'
// valor.inexistente(); // Error: Object is of type 'unknown'
// valor(); // Error: Object is of type 'unknown'
Para utilizar una variable de tipo unknown
, debemos realizar una comprobación de tipo o una aserción de tipo:
let valor: unknown = "Hola TypeScript";
// Usando comprobación de tipo
if (typeof valor === "string") {
console.log(valor.toUpperCase()); // Funciona correctamente
}
// Usando type assertion (veremos más sobre esto en otra sección)
console.log((valor as string).toUpperCase());
Diferencias clave entre unknown y any
La principal diferencia entre estos tipos radica en cómo TypeScript maneja las restricciones de tipo:
- 1. Asignabilidad:
any
puede ser asignado a cualquier otro tipo, mientras queunknown
solo puede ser asignado aany
o a sí mismo.
let valorAny: any = 10;
let valorUnknown: unknown = 20;
let numero: number;
let texto: string;
numero = valorAny; // Permitido
texto = valorAny; // Permitido
// numero = valorUnknown; // Error: Type 'unknown' is not assignable to type 'number'
// texto = valorUnknown; // Error: Type 'unknown' is not assignable to type 'string'
- 2. Operaciones permitidas: Con
any
podemos realizar cualquier operación, mientras que conunknown
debemos verificar el tipo antes.
function procesarDatos(datos: any) {
// Con any, TypeScript no verifica nada
return datos.length * 2;
}
function procesarDatosSeguros(datos: unknown) {
// Con unknown, debemos verificar el tipo
if (Array.isArray(datos) || typeof datos === "string") {
return datos.length * 2;
}
return 0;
}
- 3. Propagación de tipos:
any
"contamina" otros tipos, mientras queunknown
mantiene la seguridad de tipos.
function ejemploAny(valor: any) {
const resultado = valor.metodo(); // No hay error
return resultado.propiedad; // resultado es implícitamente any
}
function ejemploUnknown(valor: unknown) {
// const resultado = valor.metodo(); // Error: Object is of type 'unknown'
// Debemos verificar el tipo primero
if (typeof valor === "object" && valor !== null && "metodo" in valor) {
const resultado = valor.metodo();
// Aún necesitaríamos verificar el tipo de resultado
return resultado;
}
return undefined;
}
Cuándo usar cada uno
- Usa
unknown
cuando:
- Recibes datos de fuentes externas (API, entrada de usuario)
- No conoces el tipo exacto pero quieres mantener la seguridad de tipos
- Quieres forzar verificaciones de tipo antes de realizar operaciones
// Ejemplo con JSON de una API
async function obtenerDatos(): Promise<unknown> {
const respuesta = await fetch('https://api.ejemplo.com/datos');
return await respuesta.json(); // El tipo real es desconocido
}
async function mostrarNombre() {
const datos = await obtenerDatos();
// Verificamos la estructura antes de usar
if (typeof datos === 'object' && datos !== null && 'nombre' in datos) {
console.log(datos.nombre);
}
}
- Usa
any
cuando:
- Estás migrando código JavaScript a TypeScript gradualmente
- Trabajas con bibliotecas sin definiciones de tipos
- Necesitas desactivar temporalmente el sistema de tipos (aunque es mejor evitarlo)
// Ejemplo con una biblioteca sin tipos
declare const bibliotecaSinTipos: any;
// Podemos usar cualquier propiedad o método
bibliotecaSinTipos.metodoDesconocido();
Mejores prácticas
- Evita
any
siempre que sea posible, ya que anula los beneficios de TypeScript - Prefiere
unknown
sobreany
cuando necesites flexibilidad - Combina
unknown
con guardas de tipo para mantener la seguridad - Considera usar tipos genéricos como alternativa a
unknown
cuando sea apropiado
// Mejor que usar unknown directamente
function procesar<T>(valor: T): T {
// Trabajamos con un tipo genérico
return valor;
}
// Mejor que usar any
interface ResultadoAPI {
nombre?: string;
edad?: number;
[key: string]: unknown;
}
El tipo unknown
representa un equilibrio entre la flexibilidad de any
y la seguridad del sistema de tipos de TypeScript, permitiéndonos trabajar con datos de tipo desconocido de manera segura y controlada.
never y casos imposibles
En TypeScript, el tipo never
representa valores que nunca ocurren. A diferencia de otros tipos como unknown
o any
, que permiten cierta flexibilidad, never
es el tipo más restrictivo posible, indicando que algo es lógicamente imposible.
Entendiendo el tipo never
El tipo never
se utiliza para representar:
- Funciones que nunca retornan (lanzan excepciones o tienen bucles infinitos)
- Variables que no pueden tener ningún valor
- Tipos que son lógicamente imposibles después de un análisis de control de flujo
// Función que nunca retorna porque lanza una excepción
function lanzarError(mensaje: string): never {
throw new Error(mensaje);
}
// Función que nunca retorna por tener un bucle infinito
function bucleInfinito(): never {
while (true) {
// código que se ejecuta indefinidamente
}
}
Never en uniones de tipos
Una característica importante de never
es que desaparece en uniones de tipos. Esto tiene sentido lógico: si un valor puede ser de tipo A o "nunca ocurre", entonces solo puede ser de tipo A.
type UnionConNever = string | number | never;
// El tipo resultante es simplemente string | number
// Esto es útil en tipos condicionales
type SoloStrings<T> = T extends string ? T : never;
// Ejemplos de uso:
type A = SoloStrings<string | number>; // tipo: string
type B = SoloStrings<number>; // tipo: never
Casos de uso prácticos
1. Funciones exhaustivas con switch
Uno de los usos más comunes de never
es garantizar que todos los casos posibles se manejen en una estructura de control:
type Forma = "círculo" | "cuadrado" | "triángulo";
function calcularArea(forma: Forma): number {
switch (forma) {
case "círculo":
return Math.PI * Math.pow(10, 2); // Radio fijo de 10 para simplificar
case "cuadrado":
return 100; // Lado 10
case "triángulo":
return 50; // Base 10, altura 10
default:
// Esta función se ejecutará si añadimos un nuevo tipo a Forma
// y olvidamos manejarlo en el switch
const verificarExhaustividad: never = forma;
return verificarExhaustividad;
}
}
Si añadimos un nuevo tipo a Forma
(por ejemplo, "rectángulo") sin actualizar el switch
, TypeScript mostrará un error porque no se puede asignar "rectángulo" al tipo never
.
2. Manejo de casos imposibles
never
es útil para manejar casos que deberían ser lógicamente imposibles:
type ResultadoOperacion =
| { estado: "éxito"; valor: number }
| { estado: "error"; mensaje: string };
function procesarResultado(resultado: ResultadoOperacion): number {
if (resultado.estado === "éxito") {
return resultado.valor;
} else if (resultado.estado === "error") {
throw new Error(resultado.mensaje);
} else {
// Este caso nunca debería ocurrir si los tipos están bien definidos
const estadoImposible: never = resultado;
throw new Error(`Estado imposible: ${estadoImposible}`);
}
}
3. Restricciones en tipos genéricos
never
puede usarse para crear restricciones en tipos genéricos:
// Una función que solo acepta arrays no vacíos
type ArrayNoVacio<T> = [T, ...T[]];
function primerElemento<T>(arr: ArrayNoVacio<T>): T {
return arr[0];
}
// Esto compila correctamente
primerElemento([1, 2, 3]);
// Esto genera un error en tiempo de compilación
// primerElemento([]);
4. Detección de código inalcanzable
TypeScript usa never
para detectar código inalcanzable:
function verificarValor(x: string | number) {
if (typeof x === "string") {
console.log("Es un string:", x.toUpperCase());
} else if (typeof x === "number") {
console.log("Es un número:", x.toFixed(2));
} else {
// TypeScript infiere que x es de tipo 'never' aquí
// porque todos los casos posibles ya fueron manejados
console.log("Este código nunca se ejecutará");
// Podemos usar esto para verificar en tiempo de compilación
const imposible: never = x;
return imposible;
}
}
Never vs void
Es importante distinguir entre never
y void
:
void
representa funciones que retornan pero no devuelven un valor útil (retornanundefined
)never
representa funciones que nunca retornan en absoluto
// Retorna void (implícitamente undefined)
function logMensaje(mensaje: string): void {
console.log(mensaje);
// Retorna implícitamente undefined
}
// Nunca retorna
function fallar(mensaje: string): never {
throw new Error(mensaje);
// El código después de throw es inalcanzable
}
Inferencia de never en análisis de flujo
TypeScript puede inferir el tipo never
durante el análisis de flujo de control:
function ejemplo(x: string | number) {
if (typeof x === "string") {
// x es string aquí
} else if (typeof x === "number") {
// x es number aquí
} else {
// x es never aquí, porque hemos agotado todas las posibilidades
x; // TypeScript infiere que x es de tipo never
}
}
Casos imposibles con tipos condicionales
Los tipos condicionales pueden producir never
cuando una condición es imposible:
type ExtractNumbersFromUnion<T> = T extends number ? T : never;
// Ejemplos:
type Numbers = ExtractNumbersFromUnion<string | number | boolean>;
// El resultado es: number
type NoNumbers = ExtractNumbersFromUnion<string | boolean>;
// El resultado es: never (no hay números en la unión)
Este patrón es tan útil que TypeScript incluye utilidades de tipos similares como Extract
y Exclude
.
Conclusión práctica
El tipo never
es una herramienta poderosa para:
- Detectar casos imposibles en tiempo de compilación
- Garantizar que el código maneje todos los casos posibles
- Crear restricciones de tipo más precisas
- Mejorar la seguridad de tipos en situaciones complejas
Aunque puede parecer un tipo abstracto, never
tiene aplicaciones prácticas importantes que ayudan a crear código más robusto y a detectar errores potenciales antes de que ocurran en tiempo de ejecución.
void vs undefined vs null
En TypeScript, los tipos void
, undefined
y null
representan la ausencia de valor, pero cada uno tiene un propósito específico y comportamiento diferente dentro del sistema de tipos. Entender las diferencias entre ellos es fundamental para escribir código TypeScript preciso y evitar errores sutiles.
El tipo void
El tipo void
en TypeScript representa específicamente la ausencia de un valor de retorno en una función. A diferencia de JavaScript, donde todas las funciones retornan al menos undefined
, TypeScript usa void
para indicar que una función no está diseñada para devolver información útil.
// Función que no devuelve ningún valor útil
function mostrarMensaje(mensaje: string): void {
console.log(mensaje);
// No hay return explícito
}
// También se puede usar con funciones flecha
const registrarEvento = (evento: string): void => {
console.log(`Evento registrado: ${evento}`);
};
Es importante entender que una función con tipo de retorno void
puede técnicamente devolver undefined
de forma explícita, pero no otros valores:
function ejemploVoid(): void {
return undefined; // Esto está permitido
// return "hola"; // Error: Type 'string' is not assignable to type 'void'
}
El tipo void
también es útil cuando trabajamos con callbacks que no necesitan devolver valores:
function procesarDatos(datos: string[], callback: (item: string) => void): void {
for (const item of datos) {
callback(item);
}
}
// Uso
procesarDatos(["a", "b", "c"], (item) => {
console.log(`Procesando: ${item}`);
});
El tipo undefined
undefined
es tanto un valor como un tipo en TypeScript. Representa una variable que ha sido declarada pero no inicializada, o una propiedad que no existe en un objeto.
// Variable con tipo undefined explícito
let sinValor: undefined = undefined;
// Variable que puede ser string o undefined
let nombreOpcional: string | undefined;
// Función que puede devolver un string o undefined
function buscarUsuario(id: number): string | undefined {
// Si no se encuentra el usuario
if (id < 0) {
return undefined;
}
return "Usuario encontrado";
}
A diferencia de void
, que se usa principalmente para tipos de retorno de funciones, undefined
se usa cuando un valor puede estar ausente:
// Objeto con propiedades opcionales
interface Usuario {
nombre: string;
apellido?: string; // El tipo real es string | undefined
}
const usuario: Usuario = {
nombre: "Ana"
// apellido está implícitamente undefined
};
El tipo null
null
también es tanto un valor como un tipo en TypeScript. A diferencia de undefined
, que representa algo que no ha sido definido, null
representa la ausencia intencional de un valor.
// Variable con tipo null explícito
let valorNulo: null = null;
// Variable que puede ser número o null
let cantidadOpcional: number | null = null;
// Función que devuelve null para indicar un error específico
function dividir(a: number, b: number): number | null {
if (b === 0) {
return null; // Indicamos explícitamente que no se puede realizar la operación
}
return a / b;
}
Diferencias clave entre void, undefined y null
1. Propósito semántico
- void: "Esta función no devuelve un valor útil"
- undefined: "Este valor aún no ha sido asignado o no existe"
- null: "Este valor está intencionalmente vacío o no es válido"
2. Asignabilidad
La asignabilidad entre estos tipos depende de la configuración strictNullChecks
en el tsconfig.json
:
Con strictNullChecks: false
(comportamiento menos seguro):
// Con strictNullChecks desactivado
let v: void = undefined;
let v2: void = null; // Permitido cuando strictNullChecks es false
let u: undefined = undefined;
let n: null = null;
Con strictNullChecks: true
(recomendado para código más seguro):
// Con strictNullChecks activado
let v: void = undefined; // Permitido
// let v2: void = null; // Error: Type 'null' is not assignable to type 'void'
let u: undefined = undefined;
let n: null = null;
// undefined y null no son asignables entre sí
// let u2: undefined = null; // Error
// let n2: null = undefined; // Error
3. Uso en tipos de unión
Los tres tipos se comportan de manera diferente en uniones de tipos:
// Función que puede devolver string o undefined
function obtenerNombre(id: number): string | undefined {
// ...
}
// Función que puede devolver number o null
function calcularTotal(items: number[]): number | null {
// ...
}
// void rara vez se usa en uniones porque no tiene sentido semántico
// Una función no puede devolver "un valor o ningún valor"
// type Incorrecto = string | void;
4. Uso con operadores de acceso opcional
TypeScript proporciona operadores especiales para trabajar con valores potencialmente undefined
o null
:
// Operador de encadenamiento opcional (?.)
interface Usuario {
direccion?: {
calle?: string;
};
}
const usuario: Usuario = {};
const calle = usuario.direccion?.calle; // undefined en lugar de error
// Operador de coalescencia nula (??)
function mostrarNombre(nombre: string | null | undefined): string {
return nombre ?? "Anónimo"; // Usa "Anónimo" si nombre es null o undefined
}
Casos de uso prácticos
Uso de void
El tipo void
se utiliza principalmente para:
- Funciones que realizan efectos secundarios sin devolver valores
- Definir tipos de callbacks que no necesitan devolver información
- APIs de eventos donde solo interesa la acción, no el resultado
// Manejadores de eventos
interface EventHandler {
onClick: (event: MouseEvent) => void;
onHover: (event: MouseEvent) => void;
}
// Métodos de clase que modifican estado interno
class Contador {
private valor: number = 0;
incrementar(): void {
this.valor++;
}
obtenerValor(): number {
return this.valor;
}
}
Uso de undefined
El tipo undefined
es útil para:
- Parámetros opcionales
- Propiedades opcionales en interfaces
- Valores que pueden no estar disponibles todavía
// Parámetros opcionales
function saludar(nombre: string, titulo?: string) {
if (titulo === undefined) {
return `Hola, ${nombre}`;
}
return `Hola, ${titulo} ${nombre}`;
}
// Inicialización tardía
class ComponenteApp {
private config: AppConfig | undefined;
inicializar(opciones: OpcionesInicializacion): void {
// Configuración cargada después de inicializar
this.config = cargarConfiguracion(opciones);
}
estaInicializado(): boolean {
return this.config !== undefined;
}
}
Uso de null
El tipo null
es apropiado para:
- Indicar la ausencia intencional de un valor
- Representar estados de error o inválidos
- Valores que pueden ser explícitamente vacíos
// Representar errores específicos
interface ResultadoOperacion<T> {
datos: T | null;
error: Error | null;
}
function dividir(a: number, b: number): ResultadoOperacion<number> {
if (b === 0) {
return {
datos: null,
error: new Error("División por cero")
};
}
return {
datos: a / b,
error: null
};
}
// Uso con bases de datos
interface UsuarioDB {
id: number;
nombre: string;
fechaBaja: Date | null; // null indica que el usuario está activo
}
Mejores prácticas
- Activa
strictNullChecks
para obtener mayor seguridad de tipos - Usa
undefined
para valores opcionales o no inicializados - Usa
null
para ausencias intencionales o estados inválidos - Usa
void
exclusivamente para tipos de retorno de funciones - Prefiere uniones discriminadas para manejar casos de error en lugar de solo usar
null
// Mejor que usar null para errores
type Resultado<T> =
| { exito: true; valor: T }
| { exito: false; error: string };
function obtenerDatos(): Resultado<string[]> {
try {
const datos = ["dato1", "dato2"];
return { exito: true, valor: datos };
} catch (e) {
return {
exito: false,
error: e instanceof Error ? e.message : "Error desconocido"
};
}
}
// Uso
const resultado = obtenerDatos();
if (resultado.exito) {
console.log(resultado.valor);
} else {
console.error(resultado.error);
}
Entender las diferencias entre void
, undefined
y null
te permite expresar con mayor precisión la intención de tu código y aprovechar al máximo el sistema de tipos de TypeScript para crear aplicaciones más robustas y mantenibles.
Type assertions seguras
Las aserciones de tipo (type assertions) en TypeScript nos permiten indicar al compilador que trate un valor como un tipo específico, independientemente de lo que el sistema de inferencia de tipos haya determinado. Sin embargo, estas aserciones pueden ser peligrosas si no se utilizan correctamente, ya que pueden llevar a errores en tiempo de ejecución.
Entendiendo las aserciones de tipo
Una aserción de tipo es esencialmente una forma de decirle a TypeScript: "confía en mí, sé que este valor es de este tipo específico". Existen dos sintaxis para realizar aserciones:
// Sintaxis con "as"
const valor = obtenerValor() as string;
// Sintaxis con "<>" (menos común y no funciona en JSX)
const valor = <string>obtenerValor();
Aunque las aserciones nos dan flexibilidad, también pueden ser una fuente de problemas si no se utilizan con cuidado:
// Esto compila pero fallará en tiempo de ejecución
const valor: unknown = 42;
const longitud = (valor as string).length; // ¡Error en tiempo de ejecución!
Aserciones seguras con comprobaciones previas
La forma más segura de realizar aserciones es combinarlas con comprobaciones de tipo en tiempo de ejecución:
function procesarTexto(valor: unknown): number {
// Verificamos primero que realmente es un string
if (typeof valor === "string") {
// Esta aserción es segura porque ya verificamos el tipo
return (valor as string).length;
}
return 0;
}
En este ejemplo, la aserción es redundante porque TypeScript ya infiere que valor
es un string dentro del bloque if
. Sin embargo, hay casos donde las comprobaciones son más complejas y las aserciones siguen siendo necesarias.
Aserciones con tipos personalizados
Para tipos personalizados, podemos crear funciones de guarda que nos ayuden a realizar aserciones seguras:
interface Usuario {
id: number;
nombre: string;
email: string;
}
// Función de guarda para verificar si un objeto es un Usuario
function esUsuario(obj: unknown): obj is Usuario {
return (
typeof obj === "object" &&
obj !== null &&
"id" in obj &&
"nombre" in obj &&
"email" in obj &&
typeof (obj as any).id === "number" &&
typeof (obj as any).nombre === "string" &&
typeof (obj as any).email === "string"
);
}
function procesarUsuario(datos: unknown) {
if (esUsuario(datos)) {
// Aquí datos es de tipo Usuario gracias a la función de guarda
console.log(`Usuario: ${datos.nombre}, Email: ${datos.email}`);
} else {
console.log("Datos no válidos");
}
}
El operador de aserción no nula (!)
TypeScript incluye un operador especial !
para aserciones no nulas, que nos permite indicar que un valor no será null
o undefined
:
function obtenerElemento(id: string) {
// El operador ! asegura que el resultado no será null
const elemento = document.getElementById(id)!;
// Sin el operador ! tendríamos que hacer:
// const elementoTemp = document.getElementById(id);
// if (elementoTemp === null) throw new Error(`Elemento con id ${id} no encontrado`);
// const elemento = elementoTemp;
return elemento;
}
Este operador debe usarse con extrema precaución, ya que si el valor resulta ser null
o undefined
, obtendremos un error en tiempo de ejecución.
Aserciones dobles para casos especiales
En algunas situaciones, TypeScript no permite aserciones directas entre tipos no relacionados. Para estos casos, podemos usar una aserción doble pasando primero por unknown
:
// Esto generaría un error porque los tipos no están relacionados
// const valor = 42 as string;
// Aserción doble a través de unknown
const valor = 42 as unknown as string;
Esta técnica debe usarse solo en casos excepcionales donde estamos absolutamente seguros de lo que hacemos, como al trabajar con APIs externas o al implementar patrones muy específicos.
Alternativas más seguras a las aserciones
En lugar de depender de aserciones, podemos usar enfoques más seguros:
- 1. Tipos genéricos para preservar la información de tipos:
function primera<T>(array: T[]): T | undefined {
return array.length > 0 ? array[0] : undefined;
}
// TypeScript infiere correctamente el tipo
const primerNombre = primera(["Ana", "Juan", "Carlos"]);
- 2. Tipos de unión discriminados para manejar múltiples tipos posibles:
type Resultado<T> =
| { exito: true; datos: T }
| { exito: false; error: string };
function procesar<T>(valor: T): Resultado<T> {
try {
// Procesamiento...
return { exito: true, datos: valor };
} catch (e) {
return {
exito: false,
error: e instanceof Error ? e.message : "Error desconocido"
};
}
}
// Uso seguro sin aserciones
const resultado = procesar("datos");
if (resultado.exito) {
console.log(resultado.datos); // TypeScript sabe que es string
} else {
console.log(resultado.error); // TypeScript sabe que es string
}
- 3. El operador
satisfies
(disponible en TypeScript 4.9+) para verificar que un valor cumple con un tipo sin cambiar el tipo inferido:
type OpcionesTema = {
modo: "claro" | "oscuro";
acento: string;
contraste: number;
};
const tema = {
modo: "oscuro",
acento: "#007bff",
contraste: 0.8,
// Propiedades adicionales que queremos mantener
animaciones: true
} satisfies OpcionesTema;
// TypeScript verifica que el objeto cumple con OpcionesTema
// pero mantiene el tipo literal exacto incluyendo 'animaciones'
console.log(tema.animaciones); // Funciona correctamente
Patrones seguros para APIs externas
Cuando trabajamos con APIs externas o datos JSON, a menudo necesitamos convertir datos de tipo unknown
a tipos específicos. Aquí hay un patrón seguro:
interface ProductoAPI {
id: number;
nombre: string;
precio: number;
disponible: boolean;
}
// Función validadora que convierte datos desconocidos en un tipo seguro
function validarProducto(datos: unknown): ProductoAPI {
if (!datos || typeof datos !== "object") {
throw new Error("Datos de producto inválidos");
}
const producto = datos as any;
if (
typeof producto.id !== "number" ||
typeof producto.nombre !== "string" ||
typeof producto.precio !== "number" ||
typeof producto.disponible !== "boolean"
) {
throw new Error("Estructura de producto inválida");
}
return {
id: producto.id,
nombre: producto.nombre,
precio: producto.precio,
disponible: producto.disponible
};
}
// Uso
async function obtenerProducto(id: number): Promise<ProductoAPI> {
const respuesta = await fetch(`https://api.ejemplo.com/productos/${id}`);
const datos = await respuesta.json();
// Validamos y convertimos los datos a un tipo seguro
return validarProducto(datos);
}
Bibliotecas para validación de tipos
Para aplicaciones complejas, considerar el uso de bibliotecas de validación como Zod, io-ts o Yup, que proporcionan formas seguras de validar y transformar datos:
import { z } from "zod";
// Definimos un esquema de validación
const ProductoSchema = z.object({
id: z.number(),
nombre: z.string(),
precio: z.number().positive(),
disponible: z.boolean()
});
// Inferimos el tipo a partir del esquema
type Producto = z.infer<typeof ProductoSchema>;
async function obtenerProducto(id: number): Promise<Producto> {
const respuesta = await fetch(`https://api.ejemplo.com/productos/${id}`);
const datos = await respuesta.json();
// Validación y conversión segura
return ProductoSchema.parse(datos);
}
Mejores prácticas para aserciones de tipo
- Evita las aserciones cuando sea posible, prefiriendo la inferencia de tipos natural
- Realiza comprobaciones de tipo antes de usar aserciones
- Usa funciones de guarda para tipos personalizados
- Encapsula las aserciones riesgosas en funciones de validación con manejo de errores
- Considera bibliotecas de validación para datos externos complejos
- Usa el operador
!
con extrema cautela, solo cuando estés absolutamente seguro - Prefiere alternativas más seguras como genéricos o tipos discriminados
Las aserciones de tipo son herramientas poderosas, pero deben usarse con responsabilidad. Siguiendo estas prácticas, podemos aprovechar la flexibilidad que ofrecen sin comprometer la seguridad de tipos que hace valioso a TypeScript.
Ejercicios de esta lección Unknown, never y tipos especiales
Evalúa tus conocimientos de esta lección Unknown, never y tipos especiales con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Funciones
Reto composición de funciones
Reto tipos especiales
Reto tipos genéricos
Módulos
Polimorfismo
Funciones TypeScript
Interfaces
Funciones puras
Reto namespaces
Funciones flecha
Polimorfismo
Operadores
Conversor de unidades
Funciones flecha
Control de flujo
Herencia
Clases
Proyecto validación de tipado
Clases y objetos
Encapsulación
Herencia
Proyecto sistema de votación
Reto genéricos con clases
Inmutabilidad
Interfaces
Funciones de alto orden
Reto map y filter
Control de flujo
Interfaces
Reto funciones orden superior
Herencia y clases abstractas
Reto tipos mapped
Herencia de clases
Reto funciones puras
Variables y constantes
Introducción a TypeScript
Reto testing unitario
Funciones de primera clase
Clases
OOP y CRUD en TypeScript
Interfaces y su implementación
Tipos genéricos
Namespaces
Proyecto calculadora gastos
Operadores y expresiones
Proyecto generador de contraseñas
Reto unión e intersección
Encapsulación
Tipos de unión e intersección
Tipos de unión e intersección
Reto hola mundo en TS
Variables y constantes
Funciones puras
Control de flujo
Introducción a TypeScript
Resolución de módulos
Control de flujo
Reto tipos de utilidad
Reto tipos literales y condicionales
Reto exportar e importar
Propiedades y métodos
Tipos de utilidad
Clases y objetos
Tipos de datos, variables y constantes
Proyecto Minigestor de tareas
Operadores
Funciones flecha y contexto
Funciones
Reto type aliases
Funciones de alto orden
Funciones y parámetros tipados
Tipos literales
Reto enums
Tipos de utilidad
Modificadores de acceso y encapsulación
Polimorfismo
Tipos genéricos
Reto módulos
Tipos literales
Inmutabilidad
Proyecto Generator de datos
Variables y constantes
Funciones de primera clase
Todas las lecciones de TypeScript
Accede a todas las lecciones de TypeScript y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Typescript
Introducción Y Entorno
Instalación Y Configuración De Typescript
Introducción Y Entorno
Tipos De Datos, Variables Y Constantes
Sintaxis
Operadores Y Expresiones
Sintaxis
Control De Flujo
Sintaxis
Funciones Y Parámetros Tipados
Sintaxis
Funciones Flecha Y Contexto
Sintaxis
Enums
Sintaxis
Type Aliases Y Aserciones De Tipo
Sintaxis
Clases Y Objetos
Programación Orientada A Objetos
Interfaces Y Su Implementación
Programación Orientada A Objetos
Modificadores De Acceso Y Encapsulación
Programación Orientada A Objetos
Herencia Y Clases Abstractas
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Decoradores Básicos
Programación Orientada A Objetos
Propiedades Y Métodos
Programación Orientada A Objetos
Inmutabilidad
Programación Funcional
Funciones Puras
Programación Funcional
Funciones De Primera Clase
Programación Funcional
Funciones De Alto Orden
Programación Funcional
Conceptos Básicos E Inmutabilidad
Programación Funcional
Funciones De Primera Clase Y Orden Superior
Programación Funcional
Composición De Funciones
Programación Funcional
Métodos Funcionales De Arrays (Map, Filter, Reduce)
Programación Funcional
Tipos Literales
Tipos Intermedios Y Avanzados
Tipos Genéricos
Tipos Intermedios Y Avanzados
Tipos De Unión E Intersección
Tipos Intermedios Y Avanzados
Tipos De Utilidad
Tipos Intermedios Y Avanzados
Unknown, Never Y Tipos Especiales
Tipos Intermedios Y Avanzados
Tipos Mapped
Tipos Intermedios Y Avanzados
Genéricos Con Clases E Interfaces
Tipos Intermedios Y Avanzados
Módulos
Namespaces Y Módulos
Namespaces
Namespaces Y Módulos
Resolución De Módulos
Namespaces Y Módulos
Exportación E Importación De Módulos
Namespaces Y Módulos
Introducción A Módulos
Namespaces Y Módulos
Testing Unitario En Typescript
Testing
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender las diferencias y usos de los tipos especiales unknown y any en TypeScript.
- Entender el tipo never y su aplicación para casos imposibles y funciones que no retornan.
- Diferenciar entre void, undefined y null, y conocer sus usos específicos.
- Aprender a realizar aserciones de tipo seguras y cuándo utilizarlas.
- Aplicar mejores prácticas para mantener la seguridad y robustez del sistema de tipos en TypeScript.