Conditional typing, czyli kilka zdań o magicznym słowie "extends" w kontekście wyrażenia warunkowego i definicji typu.
hejto.plTrochę zajęło mi wymyślenie tematu na dziś, ale koniec końców udało mi się wpaść na coś sensownego, co będe mógl rozwinąć jutro. Dzisiaj powiemy sobie o typach warunkowych.
Kojarzycie może globalne typy takie jak NonNullable, Extract, Exclude? Dobrze się domyślacie - korzystają one właśnie z tej funkcji języka TypeScript. Zerknijmy na definicję jednego z nich:
type NonNullable<T> = T extends null | undefined ? never : T;<br /> ```<br /> Na pierwszy rzukt oka ciężko powiedzieć co dokładnie się tu dzieje, ale zaraz wszystko stanie się jasne. Słowo **extends** pozwala nakładać **ograniczenia na typy generyczne**, tzn. możemy za jego pomocą sprawdzać czy na przykład typ przekazany jako parametr generyczny T jest stringiem i na tej podstawie zwracać wybrany przez nas typ. W powyższym przypadku **NonNullable** sprawdza czy pod **T** schowany jest **null** albo **undefined**. Jeżeli tak to zwracany jest **specjalny typ never**. W przeciwnym wypadku zwracamy **typ T**.<br /> Chwila moment, przecież pod T możemy podać unię kilku różnych typów! To prawda. W takim przypadku TypeScript **wywoła nasze wyrażenie warunkowe dla każdego członka unii.** Jeżeli do **NonNullable** podamy typ **string | null | undefined** to będzie to tożsame **NonNullable | NonNullable | NonNullable**. Po wykonaniu instrukcji warunkowych dostaniemy **string | never | never**.<br /> A co z tym **never**? Przecież z **NonNullable** dostaniemy w takim przypadku sam **string**. **Never** jest szczególnym typem, które tak naprawdę **znaczy 'nic'**. Jest czymś na kształt **pustego zbioru**, a **suma pustego zbioru z innym niepustym zbiorem da... niepusty zbiór**. To samo dzieje się tutaj. Nasza unia zostaje **uproszczona do string**.<br /> Teraz bez trudu powinniście zrozumieć jak działają te typy warunkowe:<br /> ```<br /> type Extract<T, U> = T extends U ? T : never;<br /> type Exclude<T, U> = T extends U ? never : T;<br /> ```<br /> Każdy element unii T jest podstawiany do wyrażenia i porównywany z U. Jeżeli są zgodne to T pozostaje w unii. Jeżeli nie, to T staje się never, a końcowo zostaje uproszczony. Voila!<br /> Jutro rozwiniemy ten temat za pomocą **słowa kluczowego infer** i prześledzimy jak możemy **wydobywać parametry generyczne z typów** lub **parametrów funkcji**. Brzmi ciekawie? ( ͡° ͜ʖ ͡°)
Kojarzycie może globalne typy takie jak NonNullable, Extract, Exclude? Dobrze się domyślacie - korzystają one właśnie z tej funkcji języka TypeScript. Zerknijmy na definicję jednego z nich:
type NonNullable<T> = T extends null | undefined ? never : T;<br /> ```<br /> Na pierwszy rzukt oka ciężko powiedzieć co dokładnie się tu dzieje, ale zaraz wszystko stanie się jasne. Słowo **extends** pozwala nakładać **ograniczenia na typy generyczne**, tzn. możemy za jego pomocą sprawdzać czy na przykład typ przekazany jako parametr generyczny T jest stringiem i na tej podstawie zwracać wybrany przez nas typ. W powyższym przypadku **NonNullable** sprawdza czy pod **T** schowany jest **null** albo **undefined**. Jeżeli tak to zwracany jest **specjalny typ never**. W przeciwnym wypadku zwracamy **typ T**.<br /> Chwila moment, przecież pod T możemy podać unię kilku różnych typów! To prawda. W takim przypadku TypeScript **wywoła nasze wyrażenie warunkowe dla każdego członka unii.** Jeżeli do **NonNullable** podamy typ **string | null | undefined** to będzie to tożsame **NonNullable | NonNullable | NonNullable**. Po wykonaniu instrukcji warunkowych dostaniemy **string | never | never**.<br /> A co z tym **never**? Przecież z **NonNullable** dostaniemy w takim przypadku sam **string**. **Never** jest szczególnym typem, które tak naprawdę **znaczy 'nic'**. Jest czymś na kształt **pustego zbioru**, a **suma pustego zbioru z innym niepustym zbiorem da... niepusty zbiór**. To samo dzieje się tutaj. Nasza unia zostaje **uproszczona do string**.<br /> Teraz bez trudu powinniście zrozumieć jak działają te typy warunkowe:<br /> ```<br /> type Extract<T, U> = T extends U ? T : never;<br /> type Exclude<T, U> = T extends U ? never : T;<br /> ```<br /> Każdy element unii T jest podstawiany do wyrażenia i porównywany z U. Jeżeli są zgodne to T pozostaje w unii. Jeżeli nie, to T staje się never, a końcowo zostaje uproszczony. Voila!<br /> Jutro rozwiniemy ten temat za pomocą **słowa kluczowego infer** i prześledzimy jak możemy **wydobywać parametry generyczne z typów** lub **parametrów funkcji**. Brzmi ciekawie? ( ͡° ͜ʖ ͡°)