programowanieProgramista Fullstack

Jak działa Exclude w TypeScript, kiedy go używać do manipulacji typami union i jakie są niuanse przy pracy z tym typem pomocniczym?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

Exclude<T, U> — to typ pomocniczy, który pojawił się w TypeScript w celu odjęcia jednego typu od drugiego, gdy wymagane jest wykluczenie niektórych wartości z typu union.

Historia pytania

Początkowo w TypeScript nie było wygodnego sposobu na odjęcie typu od innego. Przy tworzeniu uogólnionych API, jak również podczas refaktoryzacji często wymagano uzyskania "resztowego" typu — wszystkiego oprócz zabronionych wartości. Zamiast ręcznych manipulacji z union, trzeba było utrzymywać kilka podobnych interfejsów.

Problem

Na przykład, gdy mamy typ 'A | B | C', ale potrzebujemy uzyskać typ bez B. Jest to często wymagane przy budowaniu skomplikowanych parametrów wejściowych funkcji, filtrowaniu dozwolonych wartości i dynamicznym formowaniu typów.

Rozwiązanie

Exclude rozwiązuje ten problem. Jego uproszczona sygnatura jest taka:

type Exclude<T, U> = T extends U ? never : T;

Zwraca typ, który wyklucza z T wszystkie człony U.

Przykład:

type Status = 'draft' | 'published' | 'removed'; type UserVisibleStatus = Exclude<Status, 'removed'>; const visible: UserVisibleStatus = 'draft'; // OK

Kluczowe cechy:

  • Pozwala na formowanie dynamicznych typów dzięki "odejmowaniu" części z union.
  • Ułatwia refaktoryzację — przy zmianie podstawowego typu wszystkie pochodne automatycznie się aktualizują.
  • Może być używany, na przykład, do filtrowania przypadków switch lub kluczy obiektów.

Pytania z podstępem.

Czy można używać Exclude dla typów zwykłych, a nie union?

Jeśli T nie jest typem union, ale wchodzi w skład U — Exclude i tak zadziała, ale wynikiem może być never lub T, co nie zawsze jest intuicyjne.

Exclude<'a', 'a'> // wynik: never Exclude<'a', 'b'> // wynik: 'a'

Czy Exclude usuwa wszystkie odniesienia do typu w strukturze obiektu?

Nie, Exclude nie przechodzi rekurzywnie po zagnieżdżonych polach typu, wyklucza tylko na najwyższym poziomie union.

Jak działa Exclude z interfejsami i typami-objektami?

Porównuje cały typ, a nie poszczególne właściwości. Dlatego Exclude z union kilku interfejsów — usuwa tylko te, które całkowicie pokrywają się z U.

interface A { x: number }; interface B { y: string }; // Exclude<A|B, B> daje: A (B całkowicie się pokrywa)

Typowe błędy i anty-wzorce

  • Próby zastosowania Exclude dla zagnieżdżonych lub częściowych dopasowań
  • Użycie do "usunięcia" właściwości interfejsu, a nie wariantów union
  • Ignorowanie możliwości uzyskania typu never przy pełnym pokryciu typów

Przykład z życia

Negatywny przypadek

Walidacja ról użytkownika przez Exclude<UserRoles, 'admin'>, ale zapomnieliśmy, że Exclude nie ma zastosowania do zagnieżdżonych struktur — uprawnienia 'admin:sub' nie zostały wykluczone.

Zalety:

  • Prosta formacja typu ról.

Wady:

  • Nieoczywiste zachowanie z zagnieżdżonymi lub podobnymi typami; pominięta krytyczna rola.

Pozytywny przypadek

Użycie Exclude do ograniczenia publicznego API operacjami: Exclude<Action, 'delete'>, co wyklucza niebezpieczną operację.

Zalety:

  • Bezpieczeństwo na poziomie typizacji, nie można wywołać zabronionej akcji.

Wady:

  • Konieczność utrzymywania aktualnych list typów podstawowych.