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

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

    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