Przejdź do treści
webtype.orgwebtype.org

    ↑↓ ruch · ⏎ otwórz · esc zamknij

    Zagadnienie 10 z 12

    Rekurencja

    Na poziomie typów nie ma pętli, więc rekurencja to jedyny sposób przetworzenia czegoś o nieznanej długości. Kształt jest zawsze ten sam: oddziel kawałek, obsłuż go, wywołaj się na reszcie i zatrzymaj, gdy nic nie zostanie.

    Prześledź typSprawdzone kompilatorem
    WyrażenieUnwrap<Promise<Promise<string>>>
    Rozwija się dostring
    WyrażenieUnwrap<Promise<string>>
    Rozwija się dostring
    WyrażenieUnwrap<string>
    Rozwija się dostring

    Każde wywołanie rekurencyjne usuwa jedną warstwę Promise. Końcowy string to przypadek bazowy: zwróć T i zakończ.

    Definicja typu
    type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T

    Głowa i ogon

    type Reverse<T extends readonly unknown[]> =
      T extends readonly [infer H, ...infer R] ? [...Reverse<R>, H] : []
    
    // The order you REBUILD in is the whole difference:
    //   [H, ...Reverse<R>]   copies
    //   [...Reverse<R>, H]   reverses

    Gałąź fałszywa to wyjście i musi zwracać element neutralny tego, co budujesz: [] dla krotek, '' dla łańcuchów, {} dla obiektów. Zwróć tam ładunek zamiast tego, a dostaniesz błąd o jeden, widoczny dopiero na końcu.

    Akumulatory budują wprzód

    type Unique<T extends readonly unknown[], Acc extends unknown[] = []> =
      T extends readonly [infer H, ...infer R]
        ? Includes<Acc, H> extends true ? Unique<R, Acc> : Unique<R, [...Acc, H]>
        : Acc

    Parametr z wartością domyślną niesie stan, którego wywołujący nigdy nie podaje. Poza poprawnością — to dzięki temu Unique zachowuje *pierwsze* wystąpienie, a nie ostatnie — kształt rekurencji ogonowej zużywa też mniej stosu kompilatora niż składanie wyniku przy wychodzeniu.

    Istnieje limit głębokości

    // Around 1000 levels, then:
    // "Type instantiation is excessively deep and possibly infinite."

    TypeScript zatrzymuje się mniej więcej po tysiącu instancjacji. Rekurencja ogonowa znacznie podnosi ten pułap, ale limit jest realny — i dlatego nikt nie wydaje kalkulatora na typach obsługującego duże liczby.

    Najczęstsza błędna odpowiedź

    // Intent: flatten completely, to any depth.
    type Flatten<T extends readonly unknown[]> =
      T extends readonly [infer H, ...infer R]
        ? H extends readonly unknown[]
          ? [...H, ...Flatten<R>]     // <- only unwraps ONE layer
          : [H, ...Flatten<R>]
        : []
    
    Flatten<[[[1]]]>   // [[1]] — stopped one level early

    Rozwinięcie H otwiera jedną warstwę; rekurencja w nią otwiera wszystkie. Rekurencja w jednym kierunku przechodzi listę, w dwóch — drzewo, a „zeszło tylko o poziom” to prawie zawsze brak drugiego wywołania.

    Ćwiczenie

    Rozpakuj obietnicę, niezależnie od tego, ile ma warstw.

    Spróbuj

    2
    • Unwrap<Promise<string>>—
      → string
    • Unwrap<Promise<Promise<number>>>—
      → number

    Do zapamiętania

    Podziel, obsłuż, wywołaj się, zatrzymaj. Przypadek bazowy zwraca element neutralny, a kierunek składania odróżnia kopię od odwrócenia, filtra czy mapowania.

    Ćwiczone w