Exclude<T, U> usuwa człony z typu unii, Extract<T, U> zostawia tylko pasujące człony, a NonNullable<T> usuwa null i undefined. Wszystkie trzy filtrują unie; nie ruszają właściwości obiektów.
Jak każdy typ narzędziowy, istnieją one tylko w czasie kompilacji. Ostatnią linię odrzuca kompilator; uruchamiany JavaScript nie ma pojęcia, czym jest Visible.
Exclude, Extract i NonNullable w skrócie
| Typ | Zostawia | Przykład | Wynik |
|---|---|---|---|
Exclude<T, U> | człony T, których nie da się przypisać do U | Exclude<"a" | "b" | "c", "a"> | "b" | "c" |
Extract<T, U> | człony T, które da się przypisać do U | Extract<"a" | "b" | "c", "a" | "z"> | "a" |
NonNullable<T> | wszystko oprócz null i undefined | NonNullable<string | null | undefined> | string |
Kluczowe jest sformułowanie „da się przypisać do U”. U nie musi być listą dokładnych członów; może to być szerszy typ, do którego pasuje kilka członów:
Wybieranie członów unii po kształcie
W unii dyskryminowanej Extract z częściowym kształtem wyciąga warianty, które ten kształt mają. To typowy sposób na nazwanie jednego wariantu unii pochodzącej z cudzego kodu:
Kształt { type: "click" } jest nadtypem tylko wariantu kliknięcia, więc Extract zostawia właśnie ten wariant. Kształt pasujący do kilku wariantów (na przykład { x: number }, gdyby miały go dwa) zostawia wszystkie z nich.
NonNullable
NonNullable<T> to typowa wersja sprawdzenia !== null && !== undefined. Pojawia się, gdy wyprowadzasz typ z czegoś opcjonalnego:
User["manager"] to { name: string } | null | undefined (znak ? dodaje undefined), a NonNullable usuwa oba. Od TypeScriptu 5.5 kompilator sam wnioskuje taki predykat, więc raw.filter((s) => s != null) ma już typ string[]; jawne s is NonNullable<typeof s> pokazuje intencję i działa w starszych wersjach. Definicja to type NonNullable<T> = T & {}: pusty typ obiektowy {} przyjmuje każdą wartość oprócz null i undefined, więc przecięcie z nim usuwa dokładnie te dwie.
Jak są zbudowane
Exclude i Extract to jednolinijkowe typy warunkowe w bibliotece standardowej:
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
Gdy T jest unią, typ warunkowy jest stosowany do każdego członu osobno (rozdziela się, czyli jest dystrybutywny), a człony never znikają z wyniku. Dlatego Exclude<"a" | "b", "a"> staje się ("a" extends "a" ? never : "a") | ("b" extends "a" ? never : "b"), czyli never | "b", czyli "b". Dystrybucję szczegółowo opisuje strona o typach warunkowych.
Exclude a Omit, Extract a Pick
Nazwy łatwo pomylić, bo obie pary coś „usuwają” albo „zostawiają”. Różnica polega na tym, na czym działają:
| Działa na | Usuwa lub zostawia | Przykład | |
|---|---|---|---|
Exclude<T, U> | unii | człony unii | Exclude<"a" | "b", "a"> → "b" |
Extract<T, U> | unii | człony unii | Extract<"a" | "b", "a"> → "a" |
Omit<T, K> | typie obiektowym | właściwości | Omit<User, "password"> |
Pick<T, K> | typie obiektowym | właściwości | Pick<User, "name"> |
Użycie Exclude na typie obiektowym to częsta pomyłka, która nie zgłasza żadnego błędu:
Account nie da się przypisać do "password", więc Exclude zostawia go w całości. Sam Omit jest zbudowany z Exclude zastosowanego do kluczy: Omit<T, K> to Pick<T, Exclude<keyof T, K>>. Więcej znajdziesz na stronie o Omit.
Ściślejszy Exclude
Drugi parametr Exclude przyjmuje dowolny typ, więc literówka niczego nie usuwa i nie zgłasza błędu. Dodanie ograniczenia sprawia, że kompilator sprawdza, czy to, co wykluczasz, naprawdę jest w unii:
index.ts(7,37): error TS2344: Type '"drafft"' does not satisfy the constraint 'Status'.
Po poprawieniu pisowni kod się kompiluje, a Strict jest równy "published" | "archived". Ta sama sztuczka działa dla Extract i dla ścisłego Omit (K extends keyof T).
Najczęściej zadawane pytania
Co robi Exclude w TypeScript?
Exclude<T, U> usuwa z unii T każdy człon, który da się przypisać do U. Exclude<"a" | "b" | "c", "a"> to "b" | "c". Aby usunąć kilka członów, przekaż unię jako U: Exclude<T, "a" | "b">.
Czym różni się Exclude od Extract?
Działają odwrotnie. Exclude<T, U> zostawia człony T, których nie da się przypisać do U; Extract<T, U> zostawia tylko te, które da się przypisać. Dla tych samych T i U oba wyniki razem dają T.
Czym różni się Exclude od Omit?
Exclude działa na typie unii i usuwa jej człony. Omit działa na typie obiektowym i usuwa właściwości. Exclude<User, "password"> nie robi nic przydatnego (zwraca User), a Omit<User, "password"> daje typ obiektowy bez tej właściwości. Omit jest zbudowany na Exclude: wyklucza klucz z keyof T.
Co robi NonNullable w TypeScript?
NonNullable<T> usuwa z typu null i undefined: NonNullable<string | null | undefined> to string. Jest zdefiniowany jako T & {}, bo {} przyjmuje każdą wartość oprócz null i undefined.
Dlaczego Exclude nie zgłasza błędu dla członu, który nie istnieje?
Jego drugi parametr nie ma ograniczenia, więc przyjmuje dowolny typ, a literówka taka jak Exclude<Status, "drafft"> po cichu niczego nie usuwa. Zdefiniuj ściślejszą wersję, type StrictExclude<T, U extends T> = Exclude<T, U>, żeby kompilator sprawdzał, czy U jest częścią T.