Przejdź do treści
webtype.orgwebtype.org

    ↑↓ 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