Przejdź do treści
webtype.orgwebtype.orgDwanaście zagadnień · 8/12

    ↑↓ ruch · ⏎ otwórz · esc zamknij

    Zagadnienie 8 z 12

    Dystrybutywność

    Dystrybucja to reguła, która zaskakuje każdego raz, a potem tłumaczy połowę zagadkowych zachowań systemu typów. Warunek, którego sprawdzana strona to goły parametr typowy, nie wykonuje się raz — wykonuje się raz na element unii, a wyniki są z powrotem łączone.

    Co znaczy „nagi”

    type Naked<T> = T extends string ? 'yes' : 'no'
    type A = Naked<string | number>   // 'yes' | 'no'  — ran twice
    
    type Wrapped<T> = [T] extends [string] ? 'yes' : 'no'
    type B = Wrapped<string | number>  // 'no' — ran once, on the whole union

    Nagi znaczy, że parametr stoi sam po lewej stronie `extends`, nie owinięty w krotkę, tablicę, obiekt ani obietnicę. Owinięcie w cokolwiek wyłącza dystrybucję i porównuje unię jako jedną całość.

    Dystrybucja to sposób działania filtrowania

    type MyExclude<T, U> = T extends U ? never : T
    MyExclude<'a' | 'b' | 'c', 'a'>   // 'b' | 'c'
    
    // Each member becomes never or survives; unioning drops the nevers,
    // because never is the identity element of union.

    `Exclude`, `Extract` i `NonNullable` to po jednym warunku dystrybutywnym. Sztuczka polega na tym, że `never` znika z unii, więc odwzorowanie niechcianych elementów na `never` usuwa je za darmo.

    Dwa idiomy warte zapamiętania

    // Force distribution, even when you do not need the test:
    type Each<K> = K extends K ? [K] : never
    
    // Suppress it, and detect the empty union:
    type IsNever<T> = [T] extends [never] ? true : false

    `K extends K` jest trywialnie prawdziwe i istnieje wyłącznie po to, by rozdzielać. `[T] extends [never]` to jedyny pewny sposób zapytania „czy to never”, bo warunek dystrybutywny nad `never` daje `never` i nie dociera do żadnej gałęzi.

    Najczęstsza błędna odpowiedź

    // Intent: does T contain string?
    type HasString<T> = T extends string ? true : false
    
    HasString<string | number>  // boolean, not true
    // It distributed: true | false, which collapses to boolean.
    
    // Fixed: ask about the union as a whole.
    type HasString2<T> = [T] extends [string] ? true : false

    Warunek zwracający `boolean` zamiast `true` albo `false` to prawie zawsze niezamierzona dystrybucja. Unia `true` i `false` to `boolean` — dlatego objaw wygląda tak niewinnie.

    Ćwiczenie

    Usuń `null` z unii — i pozwól warunkowi się rozdystrybuować.

    Spróbuj

    2
    • NoNull<string | null>
      string
    • NoNull<null>
      never

    Do zapamiętania

    Goły parametr się rozdziela; owinięcie w `[]` to zatrzymuje. Gdy typ tajemniczo zwraca `boolean` albo `never`, dystrybucję sprawdzaj w pierwszej kolejności.

    Ćwiczone w