Utility types to typy generyczne wbudowane w TypeScript, które zamieniają jeden typ w inny. Zamiast pisać drugi typ User ze wszystkimi polami opcjonalnymi, piszesz Partial<User>; zamiast kopiować trzy pola, Pick<User, "id" | "name">. Są globalne, więc nie trzeba ich importować.
Kiedy User się zmienia, wszystkie cztery typy pochodne zmieniają się razem z nim. Biblioteka standardowa TypeScript (lib.es5.d.ts) deklaruje 22 utility types. Sekcje poniżej wymieniają je wszystkie, pogrupowane według rodzaju typu, na którym działają, z linkiem do szczegółowej strony tam, gdzie taka istnieje.
Typy obiektowe: Partial, Required, Readonly, Pick, Omit, Record
| Utility type | Co robi | Przykład |
|---|---|---|
Partial<T> | robi wszystkie właściwości opcjonalnymi | Partial<User> dla danych aktualizacji |
Required<T> | robi wszystkie właściwości wymaganymi (usuwa ?) | Required<Config> po zastosowaniu wartości domyślnych |
Readonly<T> | robi wszystkie właściwości readonly | Readonly<State> |
Pick<T, K> | zostawia tylko klucze K | Pick<User, "id" | "name"> |
Omit<T, K> | usuwa klucze K | Omit<User, "password"> |
Record<K, V> | typ obiektowy z kluczami K i wartościami V | Record<"en" | "de", string> |
Required<T> to przeciwieństwo Partial<T>; strona o Partial omawia oba, łącznie z jawnym undefined, które przepuszcza taki spread jak ten.
Typy unii: Exclude, Extract, NonNullable
| Utility type | Co robi | Przykład |
|---|---|---|
Exclude<U, M> | usuwa z unii elementy przypisywalne do M | Exclude<"a" | "b" | "c", "a"> to "b" | "c" |
Extract<U, M> | zostawia w unii elementy przypisywalne do M | Extract<string | number, number> to number |
NonNullable<T> | usuwa null i undefined | NonNullable<string | null> to string |
Te trzy działają na uniach, nie na obiektach. To najważniejsza różnica względem Pick i Omit, które przyjmują typ obiektowy i listę jego kluczy.
Typy funkcji i klas: Parameters, ReturnType i inne
| Utility type | Co robi | Przykład |
|---|---|---|
ReturnType<F> | typ zwracany przez typ funkcji | ReturnType<typeof createStore> |
Parameters<F> | typy parametrów jako krotka | Parameters<typeof fetchPage>[0] |
ConstructorParameters<C> | parametry konstruktora klasy jako krotka | ConstructorParameters<typeof Point> |
InstanceType<C> | typ instancji tworzonej przez konstruktor | InstanceType<typeof Point> |
ThisParameterType<F> | typ parametru this funkcji | ThisParameterType<typeof greet> |
OmitThisParameter<F> | typ funkcji bez parametru this | typ greet.bind(obj) |
ThisType<T> | ustala typ this w metodach literału obiektu | używany z noImplicitThis w API typu builder |
NoInfer<T> | blokuje wnioskowanie parametru typu z tej pozycji | fallback: NoInfer<C> |
typeof createOrder jest potrzebne, bo te utility types przyjmują typ, a createOrder jest wartością. Z klasami jest tak samo: typeof Point to typ konstruktora, a samo Point użyte jako typ oznacza już typ instancji.
NoInfer decyduje, skąd typ generyczny bierze swój typ:
Bez NoInfer TypeScript wywnioskowałby C z obu argumentów i rozszerzył go do "red" | "green" | "blue", więc literówka w wartości zapasowej przeszłaby bez błędu.
Typy dla stringów: Uppercase, Lowercase, Capitalize, Uncapitalize
| Utility type | Co robi | Przykład |
|---|---|---|
Uppercase<S> | zamienia typ literału stringa na wielkie litery | Uppercase<"get"> to "GET" |
Lowercase<S> | zamienia go na małe litery | Lowercase<"GET"> to "get" |
Capitalize<S> | zamienia pierwszy znak na wielką literę | Capitalize<"name"> to "Name" |
Uncapitalize<S> | zamienia pierwszy znak na małą literę | Uncapitalize<"Name"> to "name" |
Te cztery są wbudowane w kompilator, a nie napisane w TypeScript, i najbardziej przydają się w template literal types, na przykład `on${Capitalize<E>}` dla nazw handlerów zdarzeń.
Promise: Awaited
| Utility type | Co robi | Przykład |
|---|---|---|
Awaited<T> | typ, który dostajesz z await, z rozpakowaniem zagnieżdżonych promise | Awaited<Promise<Promise<number>>> to number |
Awaited<ReturnType<typeof fn>> to standardowy sposób, żeby nazwać typ wyniku funkcji async bez deklarowania go osobno.
Łączenie utility types
Utility types można zagnieżdżać. Kilka kombinacji pojawia się na tyle często, że warto znać je na pamięć:
Zagnieżdżony utility type czyta się od środka na zewnątrz: Readonly<Pick<Post, "id" | "title">> najpierw zostawia dwie właściwości, a potem robi je tylko do odczytu. Z tych samych części powstaje helper PartialBy, który robi opcjonalnymi tylko niektóre klucze; strona o Partial pokazuje go w całości.
Utility types nic nie robią w czasie działania programu
Każdy utility type znika podczas kompilacji. Wartość z typem Omit<User, "password"> może w czasie działania nadal zawierać hasło, jeśli obiekt, z którego pochodzi, je miał:
Typ ogranicza tylko to, co twój kod może odczytać. Żeby usunąć pole z danych, wyjmij je destrukturyzacją jak w ostatnich liniach, a żeby zablokować zmiany w czasie działania, użyj Object.freeze, nie Readonly. Wbudowane typy to jednolinijkowe mapped types i conditional types, więc tymi samymi narzędziami napiszesz własne.
Najczęściej zadawane pytania
Czym są utility types w TypeScript?
To typy generyczne dostarczane razem z TypeScript, które przekształcają inne typy: Partial<T> robi wszystkie właściwości opcjonalnymi, Pick<T, K> zostawia tylko część właściwości, ReturnType<F> wyciąga typ zwracany przez funkcję i tak dalej. Są zadeklarowane w bibliotece standardowej, więc używasz ich bez żadnego importu.
Czy utility types trzeba importować?
Nie. Partial, Omit, Record, ReturnType i pozostałe to typy globalne z wbudowanych plików bibliotecznych TypeScript. Pisz Partial<User> gdziekolwiek chcesz; nie potrzebujesz importu ani pakietu npm.
Jakie utility types są wbudowane w TypeScript?
22, wszystkie zadeklarowane w lib.es5.d.ts: Partial, Required, Readonly, Pick, Omit, Record, Exclude, Extract, NonNullable, Parameters, ConstructorParameters, ReturnType, InstanceType, ThisParameterType, OmitThisParameter, ThisType, NoInfer, Awaited, Uppercase, Lowercase, Capitalize i Uncapitalize.
Czy utility types zmieniają obiekty w czasie działania programu?
Nie. Opisują tylko typy i znikają z wynikowego JavaScriptu. Omit<User, "password"> nie usuwa właściwości password, a Readonly<T> niczego nie zamraża. Żeby zmienić prawdziwy obiekt, napisz kod: destrukturyzację z rest, Object.freeze i tak dalej.
Czy mogę pisać własne utility types?
Tak. Wbudowane to zwykły TypeScript: większość to jednolinijkowe mapped types albo conditional types w lib.es5.d.ts. type Nullable<T> = { [K in keyof T]: T[K] | null } to własny utility type napisany w ten sam sposób.