Saltar al contenido
webtype.orgMarcar un primitivo

    ↑↓ mover · ⏎ abrir · esc cerrar

    Patrones

    ¿Cómo evito que un `UserId` se pase donde se esperaba un `OrderId`, si ambos son solo cadenas?

    TypeScript es estructural, así que dos alias de `string` son el mismo tipo. Una propiedad fantasma los hace distintos sin cambiar el valor.

    La receta

    declare const brand: unique symbol
    
    type Brand<T, Name extends string> = T & { readonly [brand]: Name }
    
    type UserId = Brand<string, 'UserId'>
    type OrderId = Brand<string, 'OrderId'>
    
    const asUserId = (raw: string): UserId => raw as UserId
    
    declare function findUser(id: UserId): void
    
    const id = asUserId('u_1')
    findUser(id)

    La compilación compila esto y comprueba cada resultado de abajo.

    Cómo funciona

    1. 01
      declare const brand: unique symbol

      Un `unique symbol` da una clave que nada más puede producir. Una clave de cadena permitiría que un objeto ajeno satisficiera la marca por accidente.

    2. 02
      type Brand<T, Name extends string> = T & { readonly [brand]: Name }

      La intersección añade una propiedad que solo existe en el tipo. En tiempo de ejecución el valor sigue siendo exactamente la cadena de partida.

    3. 03
      const asUserId = (raw: string): UserId => raw as UserId

      Un cast, en un solo sitio, es el precio. En todo lo demás el compilador impone la distinción gratis — por eso esta función debería ser la única entrada.

    Lo que obtienes

    Dónde falla

    La marca se borra, así que `JSON.parse` te dará sin problema algo tipado como `UserId` que nunca pasó por tu constructor. Las marcas protegen tu código de sí mismo; no validan datos que cruzan una frontera.

    Conclusión

    Usa una marca cuando dos valores comparten representación y no deben compartir significado. Mantén exactamente una función que produzca cada uno.

    Véase también

    Patrones