TypeScript

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ícate

unknown 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 que unknown solo puede ser asignado a any 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 con unknown 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 que unknown 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 sobre any 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:

  1. Funciones que nunca retornan (lanzan excepciones o tienen bucles infinitos)
  2. Variables que no pueden tener ningún valor
  3. 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 (retornan undefined)
  • 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.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende TypeScript online

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

TypeScript
Test

Reto composición de funciones

TypeScript
Código

Reto tipos especiales

TypeScript
Código

Reto tipos genéricos

TypeScript
Código

Módulos

TypeScript
Test

Polimorfismo

TypeScript
Código

Funciones TypeScript

TypeScript
Código

Interfaces

TypeScript
Puzzle

Funciones puras

TypeScript
Puzzle

Reto namespaces

TypeScript
Código

Funciones flecha

TypeScript
Puzzle

Polimorfismo

TypeScript
Test

Operadores

TypeScript
Test

Conversor de unidades

TypeScript
Proyecto

Funciones flecha

TypeScript
Test

Control de flujo

TypeScript
Código

Herencia

TypeScript
Puzzle

Clases

TypeScript
Puzzle

Proyecto validación de tipado

TypeScript
Proyecto

Clases y objetos

TypeScript
Código

Encapsulación

TypeScript
Test

Herencia

TypeScript
Test

Proyecto sistema de votación

TypeScript
Proyecto

Reto genéricos con clases

TypeScript
Código

Inmutabilidad

TypeScript
Puzzle

Interfaces

TypeScript
Test

Funciones de alto orden

TypeScript
Test

Reto map y filter

TypeScript
Código

Control de flujo

TypeScript
Test

Interfaces

TypeScript
Código

Reto funciones orden superior

TypeScript
Código

Herencia y clases abstractas

TypeScript
Código

Reto tipos mapped

TypeScript
Código

Herencia de clases

TypeScript
Código

Reto funciones puras

TypeScript
Código

Variables y constantes

TypeScript
Puzzle

Introducción a TypeScript

TypeScript
Test

Reto testing unitario

TypeScript
Código

Funciones de primera clase

TypeScript
Puzzle

Clases

TypeScript
Test

OOP y CRUD en TypeScript

TypeScript
Proyecto

Interfaces y su implementación

TypeScript
Código

Tipos genéricos

TypeScript
Test

Namespaces

TypeScript
Test

Proyecto calculadora gastos

TypeScript
Proyecto

Operadores y expresiones

TypeScript
Código

Proyecto generador de contraseñas

TypeScript
Proyecto

Reto unión e intersección

TypeScript
Código

Encapsulación

TypeScript
Puzzle

Tipos de unión e intersección

TypeScript
Test

Tipos de unión e intersección

TypeScript
Puzzle

Reto hola mundo en TS

TypeScript
Código

Variables y constantes

TypeScript
Código

Funciones puras

TypeScript
Test

Control de flujo

TypeScript
Código

Introducción a TypeScript

TypeScript
Código

Resolución de módulos

TypeScript
Test

Control de flujo

TypeScript
Puzzle

Reto tipos de utilidad

TypeScript
Código

Reto tipos literales y condicionales

TypeScript
Código

Reto exportar e importar

TypeScript
Código

Propiedades y métodos

TypeScript
Código

Tipos de utilidad

TypeScript
Test

Clases y objetos

TypeScript
Código

Tipos de datos, variables y constantes

TypeScript
Código

Proyecto Minigestor de tareas

TypeScript
Proyecto

Operadores

TypeScript
Puzzle

Funciones flecha y contexto

TypeScript
Código

Funciones

TypeScript
Puzzle

Reto type aliases

TypeScript
Código

Funciones de alto orden

TypeScript
Puzzle

Funciones y parámetros tipados

TypeScript
Código

Tipos literales

TypeScript
Puzzle

Reto enums

TypeScript
Código

Tipos de utilidad

TypeScript
Puzzle

Modificadores de acceso y encapsulación

TypeScript
Código

Polimorfismo

TypeScript
Puzzle

Tipos genéricos

TypeScript
Puzzle

Reto módulos

TypeScript
Código

Tipos literales

TypeScript
Test

Inmutabilidad

TypeScript
Test

Proyecto Generator de datos

TypeScript
Proyecto

Variables y constantes

TypeScript
Test

Funciones de primera clase

TypeScript
Test

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

TypeScript

Introducción Y Entorno

Instalación Y Configuración De Typescript

TypeScript

Introducción Y Entorno

Tipos De Datos, Variables Y Constantes

TypeScript

Sintaxis

Operadores Y Expresiones

TypeScript

Sintaxis

Control De Flujo

TypeScript

Sintaxis

Funciones Y Parámetros Tipados

TypeScript

Sintaxis

Funciones Flecha Y Contexto

TypeScript

Sintaxis

Enums

TypeScript

Sintaxis

Type Aliases Y Aserciones De Tipo

TypeScript

Sintaxis

Clases Y Objetos

TypeScript

Programación Orientada A Objetos

Interfaces Y Su Implementación

TypeScript

Programación Orientada A Objetos

Modificadores De Acceso Y Encapsulación

TypeScript

Programación Orientada A Objetos

Herencia Y Clases Abstractas

TypeScript

Programación Orientada A Objetos

Polimorfismo

TypeScript

Programación Orientada A Objetos

Decoradores Básicos

TypeScript

Programación Orientada A Objetos

Propiedades Y Métodos

TypeScript

Programación Orientada A Objetos

Inmutabilidad

TypeScript

Programación Funcional

Funciones Puras

TypeScript

Programación Funcional

Funciones De Primera Clase

TypeScript

Programación Funcional

Funciones De Alto Orden

TypeScript

Programación Funcional

Conceptos Básicos E Inmutabilidad

TypeScript

Programación Funcional

Funciones De Primera Clase Y Orden Superior

TypeScript

Programación Funcional

Composición De Funciones

TypeScript

Programación Funcional

Métodos Funcionales De Arrays (Map, Filter, Reduce)

TypeScript

Programación Funcional

Tipos Literales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Genéricos

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Unión E Intersección

TypeScript

Tipos Intermedios Y Avanzados

Tipos De Utilidad

TypeScript

Tipos Intermedios Y Avanzados

Unknown, Never Y Tipos Especiales

TypeScript

Tipos Intermedios Y Avanzados

Tipos Mapped

TypeScript

Tipos Intermedios Y Avanzados

Genéricos Con Clases E Interfaces

TypeScript

Tipos Intermedios Y Avanzados

Módulos

TypeScript

Namespaces Y Módulos

Namespaces

TypeScript

Namespaces Y Módulos

Resolución De Módulos

TypeScript

Namespaces Y Módulos

Exportación E Importación De Módulos

TypeScript

Namespaces Y Módulos

Introducción A Módulos

TypeScript

Namespaces Y Módulos

Testing Unitario En Typescript

TypeScript

Testing

Accede GRATIS a TypeScript y certifícate

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.