Przejdź do treści
webtype.orgOstempluj prymityw

    ↑↓ ruch · ⏎ otwórz · esc zamknij

    Wzorce

    Jak powstrzymać przekazanie `UserId` tam, gdzie oczekiwano `OrderId`, skoro oba są zwykłymi tekstami?

    TypeScript jest strukturalny, więc dwa aliasy `string` to ten sam typ. Fantomowa właściwość czyni je różnymi, nie zmieniając wartości.

    Przepis

    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)

    Build to kompiluje i sprawdza każdy wynik poniżej.

    Jak to działa

    1. 01
      declare const brand: unique symbol

      `unique symbol` daje klucz, którego nic innego nie wytworzy. Klucz tekstowy pozwoliłby przypadkowemu obiektowi spełnić stempel.

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

      Przecięcie dokłada właściwość istniejącą wyłącznie w typie. W czasie wykonania wartość to wciąż dokładnie ten tekst, od którego zacząłeś.

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

      Jedno rzutowanie w jednym miejscu to cena. Wszędzie indziej kompilator egzekwuje rozróżnienie za darmo — i dlatego ta funkcja powinna być jedynym wejściem.

    Co dostajesz

    Gdzie to zawodzi

    Stempel jest wymazywany, więc `JSON.parse` bez oporu poda ci coś o typie `UserId`, co nigdy nie przeszło przez twój konstruktor. Stemple chronią twój kod przed nim samym; nie walidują danych przekraczających granicę.

    Wniosek

    Sięgaj po stempel, gdy dwie wartości dzielą reprezentację, a nie mogą dzielić znaczenia. Trzymaj dokładnie jedną funkcję produkującą każdą z nich.

    Zobacz też

    Wzorce