NEVER - mistä on kyse TypeScriptin oudoimmassa tyypissä?
TypeScript tuo tyypitykset JavaScriptiin
TypeScript on Microsoftin kehittämä JavaScript-laajennus, jonka keskeisin hyöty on tarjota vahva tyypitys koodin kehitys- ja käännösvaiheeseen, jotta ajonaikaiset tyypityksiin liittyvät ongelmat tulisivat etukäteen minimoiduiksi.
TypeScriptillä on tyyppi jokaiselle kahdeksalle JavaScriptissä määritellylle
primitiivityypille, jotka ovat
string, number, bigint, boolean, object, null, undefined
ja symbol. TypeScript-tyypit tukevat myös tuttuja JavaScript-rakenteita, kuten listoja, objekteja ja Map-rakennetta. Lisäksi TypeScript tukee erikoisempia tyyppitapauksia kuten
any, unknown
ja never.
TypeScript on ennen kaikkea apuväline paremman JavaScript-koodin tuottamiseen ja vain hyvin rajatuissa käyttötapauksissa TypeScript-koodista jää mitään jäljelle käännettyyn JavaScript -koodiin.
Never on outo tyyppi
Never on puolestaan TypeScriptin oma primitiivityyppi, joka tuotiin kieleen version 2.0 mukana. Tätä kirjoitettaessa TypeScript on saavuttanut version 5.2.
Never-tyypillä on oma merkityksensä muuttujien ja funktioiden paluuarvojen tyyppinä. Muuttujien yhteydessä
never edustaa sellaista koodin osaa, jota ei voida koskaan saavuttaa. Funktioiden paluuarvojen yhteydessä puolestaan
never edustaa funktiota, jota ei voida koskaan suorittaa loppuun saakka. Perehdymme
never-tyyppisten muuttujien ja funktion paluuarvojen ominaisuuksiin tarkemmin myöhemmin blogissa.
Never on TypeScript-maailman outo tyyppi, joka edustaa jokaisen muun tyypin alityyppiä, mutta ainoastaan
never itse voi olla
never-tyypin alityyppi. Tästä pian lisää tyyppien laajenteita käsittelevän otsikon alla.
Kehittäjän tarvitsee vain harvoin kirjoittaa
never-tyyppi itse koodiin. Sen sijaan, useimmiten
never-tyyppi on vain TypeScript-tulkin tekemä lopputoteamus siitä, ettei muuttujaa käsittelevä koodilohko ole TypeScript-kääntäjän ymmärryksen mukaan saavutettavissa. Tämä voi esimerkiksi auttaa kehittäjää huomaamaan koodissa osia, johon suorittavan koodin on loogisesti mahdotonta päästä.
Kaikki TypeScript-tyypit ovat neverin laajenteita
Joukko {‘A’} on joukon {‘A’, ‘B’, ‘C’} osajoukko. Näin ollen joukon {‘A’, ‘B’, ‘C’}
voidaan sanoa laajentavan (engl. extend) joukkoa {‘A’}, koska joukon {‘A’} kaikki alkiot sisältyvät joukkoon {‘A’, ‘B’, ‘C’}. Identtiset joukot ovat osajoukkoja itselleen, joten esimerkiksi joukko {‘A’} on joukon {‘A’} osajoukko.
Samalla tavalla tyyppien maailmassa voidaan esimerkiksi sanoa tyypin
string laajentavan literaalityyppiä
“foo”, koska tyyppi
string sisältää kaikki mahdolliset merkkijonovariaatiot.
Never puolestaan sisältyy vaivihkaa kaikkiin muihin tyyppeihin, koska kaikki tyypit ovat
never-tyypin laajennuksia. Toisin sanoen, TypeScript-maailmassa kaikki tyypit laajentavat
never-tyyppiä. Myös
never itse laajentaa itseään.
Never on vähän kuin tyhjä joukko joukko-opissa, sillä tyhjä joukko on kaikkien muiden joukkojen osajoukko.
TypeScript-maailmassa operoidaan joukko-opillisilla konsepteilla. Esimerkiksi, jos haluamme kirjoittaa rajapinnan (engl. interface), joka laajentaa jo olemassa olevaa rajapintaa, voimme kirjoittaa sen käyttäen TypeScriptin extends-ilmaisua.
Muutama hyödyllinen TypeScript-konsepti
Type Narrowing & Type Guard
Sen lisäksi, että TypeScript-tulkki osaa tulkita JavaScripin ajonaikaisia staattisia tyyppejä, se osaa tulkita myös koodin suoritusta ohjaavia rakenteita, kuten if/else, ternary-operaattori, toistorakenteita ja totuustarkistusoperaattoreita.
Näissä koodin ohjausrakenteissa mahdollisten tyyppien kirjo saattaa supistua (engl. type narrowing). Tyyppien kirjon supistuminen tapahtuu ns. tyyppiä vartioivassa osassa (engl. type guard).
TypeScript ymmärtää useita Type Narrowing -rakenteita. Yksi yleinen rakenne on JavaScriptin typeof-operaattori.
Esimerkkikoodin padding-muuttuja voi olla tyyppiä
number tai
string. Repeat-metodi on olemassa ainoastaan
number-tyyppisille muuttujille, mutta se on TS-tulkille okei, sillä if-lauseen type guard varmistaa, että if-blokin sisällä padding-muuttuja on aina number-tyyppiä.
Type predicate function
Joskus type guardin logiikaksi tarvitaan monimutkaisempaa koodia, jota TypeScript ei kykene automaattisesti päättelemään. Tällaisessa tapauksessa voimme luoda oman funktion tekemään päätelmä ja kertoa TypeScriptille, minkä type guard -päättelyn funktio toteuttaa. Tällainen funktio on nimeltään Type Predicate Function.
Kehittäjän on oltava tarkkana, jotta funktio todella tekee juuri oikean tyyppitarkistuksen, sillä kuten todettua, TypeScript ei funktion sisäistä logiikkaa kykene tarkastamaan, vaan uskoo täysin sen, minkä tyyppitarkastuksen kehittäjä väittää TypeScriptille funktion toteuttavan.
Type Predicate Function palauttaa aina
boolean-arvon ja tyypin tarkistuksen tiedot välitetään TypeScriptille funktion palautustyypin merkinnän yhteydessä, johon ei tällä kertaa merkitäkään
boolean, vaan muoto “[funktion parametrin nimi] is [tyypin nimi]”. Katso esimerkki alta.
Funktion logiikassa tarkastetaan, onko pet-muuttujalla property swim. Jos swim-property on olemassa, tiedämme pet-muuttujan olevan tyyppiä
Fish ja funktio palauttaa
boolean-arvon true. Nyt käyttämällä funktiota koodissa, TypeScript osaa suhtautua funktioon Type Guardina.
Exhaustiveness checking & never
Switch-case-rakenteessa on mahdollista toteuttaa
never-tyypin avulla
union-tyypin perinpohjainen alityyppien läpikäynnin tarkastus (engl. exhaustiveness checking).
Tiedämme jo useita tapoja miten union-tyyppisten muuttujien jäljellä olevia tyyppivaihtoehtoja voidaan supistaa (engl. narrow) type guardien avulla esimerkiksi typeof-operaattorilla tai Type Predicate Functionin avulla.
Kun haluamme kuitenkin erikseen varmistaa TypeScriptin avulla, että onhan kaikki tyyppivaihtoehdot nyt läpi koluttu, voimme tehdä tämän tarkistuksen merkitsemällä muuttujan
never-tyypiksi. Jos TypeScript hyväksyy
never-tyypin, unionilla ei ole enää yhtäkään tyyppivaihtoehtoa jäljellä ja muuttujan tyypit on käsitelty Type Guardeille läpikotaisin, eli sille on toteutettu “exhaustive type checking”. Katso esimerkki alta.
Koska TypeScript hyväksyy never-tyypin, tyypin exhaustiveness (tai ts. tyypin läpikotaisuustarkistus) on onnistunut, sillä myös TypeScript on sitä mieltä, ettei Shape-tyypin unionilla ole default-blokin sisällä enää yhtäkään mahdollista tyyppiä jäljellä, jota aiemmat type guardit (rivit 5 ja 7) eivät olisi jo poissulkeneet.
Never-tyyppistä arvoa ei ole olemassa
Muuttujan tyypiksi on sallittua asettaa never, mutta never-tyyppiselle muuttujalle emme voi asettaa mitään arvoa.
Mikään arvo, kuten esimerkiksi
undefined, ei ole sallittu arvo never-tyyppiselle muuttujalle.
Never on puhdas TypeScript-maailman tyyppi ja konsepti, eikä JavaScript-maailmassa ole
never-tyyppisiä arvoja.
Kiitos kun tutustuit
never-blogin ensimmäiseen osaan. Seuraavassa osassa (osa 2/2) keskitymme
neverin rooliin tyyppisupistimissa (engl. type guard), unioneissa ja leikkauksissa. Lisäksi katsomme, mikä merkitys
neverillä on funktioiden paluuarvon tyyppinä.
Nähdään toisessa osassa!