26 nov 2018

TypeScript generics och den mörka borgen

Välkommen in i borgen!

darkcastle.png

Låt oss tala om JavaScript! Dess fans hyllar språkets flexibilitet. Belackarna framhåller att den lösa typningen som möjliggör flexibiliteten innebär fler nackdelar än fördelar.

Men, så kom TypeScript! TypeScript erbjuder en kompromiss som bibehåller flexibiliteten, men mitigerar nästan hela huvudvärken som lösa typer normalt ger. Här på Edument har vi till mans blivit väldigt charmade, till den grad att det nu känns naket att skriva JavaScript utan stöd av TypeScript.

En väldigt kraftfull funktion i stark typning är generics. Det är också en funktion som är lite lurig att greppa, så i den här blogposten ska vi kika på ett exempel. 

I vår Angular-kurs så lurar vi ofta in deltagarna på att bygga en app-variant av den klassiska spelboken där läsaren själv bestämmer vad som ska hända:

gamebook.png

Själva äventyret styrs av JSON-data som delgarna skriver. Det ser ut ungefär så här:

const theDarkCastle = {
 beginning: {
 title: "The journey begins",
   description: "You wake up in a strange room. …",
   options: [
     { text: "Open the door", targetPage: 'hallway' },
     { text: "Climb out the window", targetPage: 'precipice' },
   ]
 },
 hallway: {
   title: "A dark hallway",
   description: "The hallway ends with two doors.",
   options: [
     { text: "The door on the left", targetPage: 'guardroom' },
     { text: "The door on the right", targetPage: 'staircase' },
     { text: "Go back", targetPage: 'beginning' },
   ]
 },
 // ...massa fler sidor...
}

 Att på detta sätt kunna skapa stora objekt, utan att först gå och be om lov via en klassdefinition, är ett bra exempel på JavaScripts flexibilitet.

Men! Utan klassdefinition så kan vår editor inte hjälpa oss om vi skulle råka stava fel på ”description”, eftersom editorn inte vet hur datan borde se ut!

Därför får deltagarna skapa interface med TypeScript för sin data:

type Adventure = {
 [pageId: string]: {
   title: string,
   description: string,
   options: {
     text: string
     targetPage: string
   }[]
 }
}

Genom att säga att `theDarkCastle` är av typen `Adventure` så får vi nu hjälp av editorn att hitta eventuella misstag i datastrukturen:

typo-deskription.png

 Hurra! Men, det finns ett ställe där vi inte får hjälp, nämligen när vi anger `targetPage` för en option! TypeScript tror att de bara är strängar, men det måste ju dessutom vara strängar som också är id:n för en annan ”sida” i vår data!

Vi skulle kunna lösa det genom att skapa en specifik typ för vad id:n kan vara…

type DarkCastlePageId = 'beginning' | 'hallway' | 'hallway2' | 'precipice' // ...och massa fler

 …och använda den typen i vårt interface:

type Adventure = {
 [pageId in DarkCastlePageId]: {
   title: string,
   description: string,
   options: {
     text: string
     targetPage: DarkCastlePageId
   }[]
 }
}

 Nu kan TypeScript rädda oss från att hänvisa till en obefintlig sida genom att exempelvis skriva “precipise” istället för “precipice”:

typo-presipise.png

Hurra igen! Men, vi har ett men igen. Ty appen som vi bygger är tänkt att fungera som en spelmotor - den ska kunna köra vilket äventyr som helst, så länge datan har rätt form. Men nu har vi hårdkodat den till de id:n som finns just i `theDarkCastle`-äventyret.

Det är nu vi har glädje av generics. De låter oss nämligen skicka inparametrar till typer!

type Adventure<PageIdType extends string = string> = {
 [pageId in PageIdType]: {
   title: string,
   description: string,
   options: {
     text: string
     targetPage: PageIdType
   }[]
 }
}

 Vi har just berättat för TypeScript att…

  • Adventure-typen tar en inparameter
  • Den parametern måste vara en förlängning av strängar
  • Om vi inte skickar in något, säg att de är vanliga strängar

Nu kan vi typa theDarkCastle-äventyret genom att skicka in DarkCastlePageId-typen som vi skapade tidigare till Adventure-typen…

const theDarkCastle: Adventure<DarkCastlePageId> = {
 beginning: {
   title: "The journey begins",

…och tack vare detta system så kan vi använda Adventure-typen även för helt andra äventyr!

const myLittlePony: Adventure<MyLittlePonyPageId> = {
 stable: {
   title: "In the stable",

 I vår Angularkurs så är just Angular i centrum, så där gräver vi sällan djupare i generics än vad vi gjort i denna post (även om vi ibland har visat lite svart TypeScript-magi för att autogenerar `DarkCastlePageId`-typen).

Men i TypeScriptkursen så kan vi gräva precis hur djupt vi vill, vilket brukar vara väldigt långt under jord. Det är ju först därnere som man bildar sig en förståelse för generics, och andra typverktyg, på djupet! 


banner_typescript_30_3.jpg


Relaterade kurser

  • The New Angular

    Denna kurs lär dig både hur nya Angulars API fungerar, och hur man ska tänka applikationsarkitektur när man bygger med det.

    Kursområde: Webbutveckling
    Omfattning: 2 dagar
    Kostnad: 21 500 SEK
  • TypeScript

    TypeScript tar avstamp från JavaScript och gör det till ett säkert språk. Du får gradvis typcheckning, kodkomplettering, och statisk kontroll av hur din kod hänger ihop. JavaScript har aldrig haft det hårda skyddande skal som vi är vana vid i nutida språk. TypeScript agerar som ett programmerings-exoskelett, och ger dig säkerhet, uttrycksfullhet och precision ovanpå JavaScript. 

    Kursområde: Webbutveckling
    Omfattning: 2 dagar
    Kostnad: 21 500 SEK