Keyof ja typeof
TypeScript osaa ilmaista tyyppejä monipuolisesti olemassa olevien arvojen ja tyyppien avulla. Mikäli ohjelmakoodissa on olemassa jo jokin vakioarvo, sen tyypistä voidaan muodostaa kätevästi uudelleenkäytett ävä alias typeof
-operaattorilla. Toisaalta keyof
-operaattorilla voit muodostaa unionin minkä tahansa tyypin avaimista.
Unionit ja leikkaukset ovat myös hyödyllisiä tapoja uusien tyyppien ilmaisemiseksi toisten tyyppien avulla, ja näitä käsitellään materiaalin seuraavissa osissa.
typeof
Mikäli ohjelmakoodissa on olemassa jo jokin vakioarvo, sen tyypistä voidaan muodostaa kätevästi uudelleenkäytettävä alias typeof
-operaattorilla. Otetaan esimerkiksi seuraava olio, joka sisältää kolme muuttujaa, kaksi merkkijonoa ja yhden totuusarvon:
const takeOutTrash = {
title: 'Take out the trash',
description: 'Empty the trash bin and recyclables',
completed: false,
};
Tätä olemassa olevaa oliota voidaan nyt hyödyntää esimerkiksi uuden Task
-tyypin määrittelemiseksi:
type Task = typeof takeOutTrash; // tyyppi { title: string; description: string; completed: boolean; }
Lisätietoja typeof
-operaattorista löydät TypeScriptin käsikirjasta.
On hyvä huomioida, että TypeScriptin typeof
-operaattori liittyy TypeScriptin tyyppien määrittelemiseen, kun taas JavaScriptin typeof
on ajonaikaisesti suoritettava lauseke.
Hieman epäonnisesti nämä kaksi operaatiota ovat nimetty täysin samalla tavalla, mutta tekevät eri asioita 😕:
keyof
TypeScriptissä voit luoda myös uuden tyypin, joka sisältää vakiot kaikista olemassa olevan toisen tyypin avaimista, eli käytännössä muuttujien nimistä. Tämä tapahtuu keyof
-operaattorilla.
interface Color {
red: number;
green: number;
blue: number;
}
type KeysOfColor = keyof Color; // red | green | blue
Tämä on erityisen hyödyllistä tapauksissa, joissa sinun tarvitsee käsitellä olion muuttujia, mutta et etukäteen tiedä mitä muuttujista milloinkin käytetään.
Ajattele esimerkiksi seuraavia funktioita, jotka käsittelevät värin punaista, sinistä tai vihreää komponenttia:
function incrementComponent(color: Color, component: keyof Color, percent: number) {
color[component] *= 1 + percent / 100;
}
function swapColorComponents(color: Color, componentA: keyof Color, componentB: keyof Color) {
[color[componentA], color[componentB]] = [color[componentB], color[componentA]];
}
let myColor: Color = { red: 255, green: 10, blue: 15 };
incrementComponent(myColor, 'blue', 10);
swapColorComponents(myColor, 'red', 'green');
Nyt yllä määritellyt funktiot toimivat millä tahansa värin komponentilla. TypeScript osaa silti varmistaa, että annettu merkkijono löytyy oikeasti Color
-tyypistä, eli seuraava funktiokutsu aiheuttaisi käännettäessä virheen:
incrementComponent(myColor, 'yellow', 10);
Argument of type '"yellow"' is not assignable to parameter of type 'keyof Color'
Soveltava esimerkki
Kuten useissa muissakin tapauksissa, TypeScriptin tyyppijärjestelmä taipuu myös typeof
- ja keyof
-operaattoreiden kanssa moneen. Niitä voidaan siis esimerkiksi yhdistellä samaan lausekkeeseen.
Esimerkiksi seuraavassa esimerkissä luodaan olio, jossa avaimina on t-paidan kokojen lyhenteet ja arvoina koko tekstinä. Alemmalla rivillä muodostetaan uusi Size
-tyyppi, jossa sizes
-oliosta otetaan ensin tyyppi typeof
-operaatiolla, ja tästä tyypistä otetaan avaimet keyof
-operaatiolla. Lopputuloksena on Size
-tyyppi, joka on unioni arvoista s
, m
ja l
:
let sizes = {
's': 'small',
'm': 'medium',
'l': 'large'
};
type Size = keyof typeof sizes; // 's' | 'm' | 'l'
Vaikka tämänkaltaiset käyttötapaukset ovat usein tarpeettomia, oikein käytettyinä niiden avulla voidaan vähentää koodin duplikointia ja edistää ylläpidettävyyttä, kun tyyppien määritykset päivittyvät automaattisesti, mikäli koodiin lisätään myöhemmin esimerkiksi uusia kokoja, kuten xs
ja xl
.