Genéricos en TypeScript
En este tutorial, aprenderemos sobre los genéricos en TypeScript, una característica poderosa que te permite escribir código más flexible y reutilizable.
¿Qué son los genéricos en TypeScript?
Los genéricos son una forma de crear funciones, clases o interfaces que puedan trabajar con diferentes tipos de datos, sin perder la seguridad de tipos. Es como crear "moldes" que puedes reutilizar para diferentes materiales.
Imagina que tienes una caja, y quieres que esa caja pueda guardar cualquier cosa: zapatos, libros, juguetes, etc. Los genéricos te permiten especificar qué tipo de cosa guardarás, y TypeScript te ayuda a asegurarte de que siempre usas la caja correctamente.
Recursos
Ejemplo Básico
Sin Generics (problemático)
// Función que devuelve lo mismo que recibe
function getData(data: any): any {
return data;
}
const number = getData(42);
const text = getData("Hello");
// Problema: TypeScript no sabe qué tipo devuelve
// number podría ser string, number, etc.
Con Generics (solución)
// T es como una variable para tipos
function getData<T>(data: T): T {
return data;
}
// Arrow function con generics
// const getData = <T>(data: T): T => {
// return data;
// };
const number = getData<number>(42); // number es number
const text = getData<string>("Hello"); // text es string
// O mejor aún, TypeScript puede inferir el tipo:
const number2 = getData(42); // TypeScript sabe que es number
const text2 = getData("Hello"); // TypeScript sabe que es string
Ejemplo con Fetch
// Función genérica para hacer fetch de datos
const fetchData = async <T>(url: string): Promise<T> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
};
const URL_TODO = "https://jsonplaceholder.typicode.com/todos/1";
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
fetchData<Todo>(URL_TODO)
.then((todo) => {
console.log(todo);
})
.catch((error) => {
console.error("Error fetching todo:", error);
});
const URL_POST = "https://jsonplaceholder.typicode.com/posts/1";
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
fetchData<Post>(URL_POST)
.then((post) => {
console.log(post);
})
.catch((error) => {
console.error("Error fetching post:", error);
});
Ejemplo Fetch con Vue 3
Vue es un framework de JavaScript para crear interfaces de usuario. Se basa en HTML, CSS y JavaScript estándar y proporciona un modelo de programación declarativo basado en componentes que ayuda a desarrollar eficientemente interfaces de usuario de cualquier complejidad.
npm create vite@latest .
src\types\data.type.ts
export interface Post {
userId: number;
id: number;
title: string;
body: string;
}
export interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
}
src\lib\fetch-data.ts
export const fetchData = async <T>(url: string): Promise<T> => {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error fetching data: ${response.statusText}`);
}
return response.json() as Promise<T>;
};
src\App.vue
<!-- Composition API es una de las características más poderosas de Vue 3, que permite organizar mejor el código y reutilizar la lógica de componentes. En este ejemplo, vamos a utilizar la Composition API junto con TypeScript para crear un componente que obtenga datos de una API y los muestre en la interfaz de usuario. -->
<script setup lang="ts">
import { ref } from "vue";
import { fetchData } from "./lib/fetch-data";
import type { Post, Todo } from "./types/data.type";
// Definición de variables reactivas para almacenar los datos obtenidos
const post = ref<Post | null>(null);
const todo = ref<Todo | null>(null);
const getPost = async () => {
const data = await fetchData<Post>(
"https://jsonplaceholder.typicode.com/posts/1"
);
// Asignación de los datos obtenidos a la variable reactiva
post.value = data;
};
const getTodo = async () => {
const data = await fetchData<Todo>(
"https://jsonplaceholder.typicode.com/todos/1"
);
todo.value = data;
};
</script>
<template>
<main class="container">
<h1>Genéricos en TypeScript</h1>
<div class="d-flex gap-2 mb-2">
<!-- utilizando directiva @click -->
<button
class="btn btn-outline-dark"
@click="getPost"
>
Obtener POST
</button>
<button
class="btn btn-outline-dark"
@click="getTodo"
>
Obtener TODO
</button>
</div>
<!-- utilizando v-if para mostrar los datos obtenidos -->
<article
class="card mb-2"
v-if="post"
>
<div class="card-body">
<!-- utilizando sintaxis de plantilla (interpolación) -->
<h2 class="card-tile">{{ post?.title }}</h2>
</div>
</article>
<article
class="card mb-2"
v-if="todo"
>
<div class="card-body">
<h2 class="card-tile">{{ todo?.title }}</h2>
</div>
</article>
</main>
</template>
<style scoped></style>
Restricciones con Generics
A veces queremos limitar qué tipos pueden usar nuestros generics:
// Solo aceptar objetos que tengan una propiedad 'id'
interface HasId {
id: number;
}
function findById<T extends HasId>(items: T[], id: number): T | undefined {
return items.find((item) => item.id === id);
}
// Funciona con cualquier objeto que tenga 'id'
const users = [
{ id: 1, name: "Ana", email: "ana@email.com" },
{ id: 2, name: "Carlos", email: "carlos@email.com" },
];
const products = [
{ id: 1, name: "Laptop", price: 1200 },
{ id: 2, name: "Mouse", price: 25 },
];
const foundUser = findById(users, 1);
const foundProduct = findById(products, 2);
Ventajas de los Generics
- Reutilización: Un mismo código funciona con diferentes tipos
- Seguridad: TypeScript verifica los tipos en tiempo de compilación
- Autocompletado: El editor sabe qué propiedades y métodos están disponibles
- Mantenibilidad: Código más limpio y fácil de mantener
Consejos Prácticos
- Nombres descriptivos: Usa
T
para tipo genérico simple, pero considera nombres más descriptivos comoTData
,TItem
- No abuses: No uses generics si no los necesitas realmente
- Restricciones: Usa
extends
para limitar qué tipos acepta tu generic - Inferencia: Deja que TypeScript infiera los tipos cuando sea posible
¡Los generics son una herramienta poderosa que te ayudará a escribir código más flexible y reutilizable!