Tipos de identificación en Typescript
Aquí explico un concepto que encontré usando Typescript donde uno puede usar un tipo "string literal" con "union types" para crear nuevos tipos únicos.
Esto es particularmente útil para identificadores, los cuales (espero) nunca escribimos directamente en el código sino que son leídos de un programa externo y se pasan a otro componente intactos.
Digamos que tenemos dos tipos de entidades en nuestro programa: Usuario y Producto, y ambos tienen una propiedad id del tipo ObjectId. Nada previene que accidentalmente pasemos un ID de usuario donde se espera un ID de producto, solo un error en tiempo de ejecución que asustará a nuestros usuarios y robará nuestra paz mental. Creando distintos tipos para dichos IDs (como IdUsuario e IdProducto) podemos usar el poder de Typescript para asegurarnos que nunca los mezclamos accidentalmente.
// cualquier string sirve
type UserId = 'text-random-para-UserId';
type ProductId = 'text-random-para-ProductId';
function getUser(id: UserId) {}
function getProduct(id: ProductId) {}
const userId = '' as UserId;
const productId = '' as ProductId;
getUser(userId);
getUser(productId); // error
getUser(productId as UserId); // error
getProduct(productId);
getProduct(userId); // error
getProduct(userId as ProductId); // errorEste fue el enfoque que tomé en una herramenta para crear bots de Discord en deno llamada Denord, que usa distintos tipos de identificadores como GuildId (gremio), ChannelId y MessageId.
Actualización Julio 2022: Zack llevó esto un paso más allá creando una librería que esconde la parte desagradable de esta técnica https://github.com/modfy/nominal
Actualización Agosto 2023: Esto se está convirtiendo en un patrón común llamado branded types
Actualización Febrero 2024: Branded types ahora tiene un snippet sencillo para usarlos. fuente
// si no quieres que los strings normales sean compatibles
// borra el signo de pregunta aquí -> V
export type Branded<T, K extends string> = T & { __opaque__?: K };
type UserId = Branded<string, 'User'>;
type ProductId = Branded<string, 'Product'>;
function getUser(userId: UserId) {}
const userId = '12' as UserId;
const productId = '34' as ProductId;
const random = 'hi'; // string
getUser(userId); // funciona
getUser(productId); // Type '"Product"' is not assignable to type '"User"'
getUser('hello'); // funciona por el signo de pregunta
getUser('hello' as UserId); // funciona
getUser(random); // works por el signo de pregunta
getUser(random as UserId); // funciona(diapositivas solo disponible en inglés 🇬🇧)