TypeScript generics och den mörka borgen
Edument

TypeScript generics och den mörka borgen

I denna blogpost ger vi oss in i Mörka Borgen för att lära oss om TypeScript generics!

Välkommen in i borgen!

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:

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:



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”:



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! 


Författare: David Waller

JavaScript seem to be disabled in your browser.

You must have JavaScript enabled in your browser to utilize the functionality of this website.