Svenska

Utforska type guards och type assertions i TypeScript för att öka typsäkerheten, förhindra körtidsfel och skriva mer robust och underhållbar kod. Lär dig med praktiska exempel och bästa praxis.

Bemästra typsäkerhet: En omfattande guide till type guards och type assertions

Inom mjukvaruutveckling, särskilt när man arbetar med dynamiskt typade språk som JavaScript, kan det vara en stor utmaning att upprätthålla typsäkerhet. TypeScript, ett superset av JavaScript, hanterar detta problem genom att introducera statisk typning. Men även med TypeScript's typsystem uppstår situationer där kompilatorn behöver hjälp med att härleda den korrekta typen för en variabel. Det är här type guards och type assertions kommer in i bilden. Denna omfattande guide kommer att fördjupa sig i dessa kraftfulla funktioner, med praktiska exempel och bästa praxis för att förbättra din kods tillförlitlighet och underhållbarhet.

Vad är Type Guards?

Type guards (typvakter) är TypeScript-uttryck som begränsar typen av en variabel inom ett specifikt scope. De gör det möjligt för kompilatorn att förstå en variabels typ mer exakt än den ursprungligen kunde härleda. Detta är särskilt användbart när man hanterar union-typer eller när en variabels typ beror på körtidsvillkor. Genom att använda type guards kan du undvika körtidsfel och skriva mer robust kod.

Vanliga tekniker för Type Guards

TypeScript erbjuder flera inbyggda mekanismer för att skapa type guards:

Använda typeof

typeof-operatorn är ett enkelt sätt att kontrollera den primitiva typen av en variabel. Den returnerar en sträng som indikerar typen.

function printValue(value: string | number) {
  if (typeof value === "string") {
    console.log(value.toUpperCase()); // TypeScript vet att 'value' är en sträng här
  } else {
    console.log(value.toFixed(2)); // TypeScript vet att 'value' är ett nummer här
  }
}

printValue("hello"); // Output: HELLO
printValue(3.14159); // Output: 3.14

Använda instanceof

instanceof-operatorn kontrollerar om ett objekt är en instans av en viss klass. Detta är särskilt användbart när man arbetar med arv.

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof!");
  }
}

function makeSound(animal: Animal) {
  if (animal instanceof Dog) {
    animal.bark(); // TypeScript vet att 'animal' är en Dog här
  } else {
    console.log("Generiskt djurljud");
  }
}

const myDog = new Dog("Buddy");
const myAnimal = new Animal("Generiskt djur");

makeSound(myDog); // Output: Woof!
makeSound(myAnimal); // Output: Generiskt djurljud

Använda in

in-operatorn kontrollerar om ett objekt har en specifik egenskap. Detta är användbart när man hanterar objekt som kan ha olika egenskaper beroende på deras typ.

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

function move(animal: Bird | Fish) {
  if ("fly" in animal) {
    animal.fly(); // TypeScript vet att 'animal' är en Bird här
  } else {
    animal.swim(); // TypeScript vet att 'animal' är en Fish här
  }
}

const myBird: Bird = { fly: () => console.log("Flyger"), layEggs: () => console.log("Lägger ägg") };
const myFish: Fish = { swim: () => console.log("Simmar"), layEggs: () => console.log("Lägger ägg") };

move(myBird); // Output: Flyger
move(myFish); // Output: Simmar

Anpassade Type Guard-funktioner

För mer komplexa scenarier kan du definiera dina egna type guard-funktioner. Dessa funktioner returnerar ett typpredikat, vilket är ett booleskt uttryck som TypeScript använder för att begränsa en variabels typ. Ett typpredikat har formen variable is Type.

interface Square {
  kind: "square";
  size: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Circle;

function isSquare(shape: Shape): shape is Square {
  return shape.kind === "square";
}

function getArea(shape: Shape) {
  if (isSquare(shape)) {
    return shape.size * shape.size; // TypeScript vet att 'shape' är en Square här
  } else {
    return Math.PI * shape.radius * shape.radius; // TypeScript vet att 'shape' är en Circle här
  }
}

const mySquare: Square = { kind: "square", size: 5 };
const myCircle: Circle = { kind: "circle", radius: 3 };

console.log(getArea(mySquare)); // Output: 25
console.log(getArea(myCircle)); // Output: 28.274333882308138

Vad är Type Assertions?

Type assertions (typassertioner) är ett sätt att tala om för TypeScript-kompilatorn att du vet mer om typen av en variabel än vad den för närvarande förstår. De är ett sätt att åsidosätta TypeScript's typinferens och explicit specificera en värdes typ. Det är dock viktigt att använda type assertions med försiktighet, eftersom de kan kringgå TypeScript's typkontroll och potentiellt leda till körtidsfel om de används felaktigt.

Type assertions har två former:

as-nyckelordet föredras generellt eftersom det är mer kompatibelt med JSX.

När man ska använda Type Assertions

Type assertions används vanligtvis i följande scenarier:

Exempel på Type Assertions

Explicit Typassertion

I det här exemplet hävdar (assert) vi att anropet till document.getElementById kommer att returnera ett HTMLCanvasElement. Utan assertionen skulle TypeScript härleda en mer generisk typ av HTMLElement | null.

const canvas = document.getElementById("myCanvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d"); // TypeScript vet att 'canvas' är ett HTMLCanvasElement här

if (ctx) {
  ctx.fillStyle = "#FF0000";
  ctx.fillRect(0, 0, 150, 75);
}

Arbeta med okända typer

När man arbetar med data från en extern källa, som ett API, kan man få data med en okänd typ. Du kan använda en type assertion för att berätta för TypeScript hur den ska behandla datan.

interface User {
  id: number;
  name: string;
  email: string;
}

async function fetchUser(id: number): Promise<User> {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  const data = await response.json();
  return data as User; // Assertera att datan är en User
}

fetchUser(1)
  .then(user => {
    console.log(user.name); // TypeScript vet att 'user' är en User här
  })
  .catch(error => {
    console.error("Fel vid hämtning av användare:", error);
  });

Försiktighetsåtgärder vid användning av Type Assertions

Type assertions bör användas sparsamt och med försiktighet. Överanvändning av type assertions kan dölja underliggande typfel och leda till körtidsproblem. Här är några viktiga överväganden:

Typinskränkning (Type Narrowing)

Type guards är oupplösligt kopplade till konceptet type narrowing (typinskränkning). Typinskränkning är processen att förfina typen av en variabel till en mer specifik typ baserat på körtidsvillkor eller kontroller. Type guards är de verktyg vi använder för att uppnå typinskränkning.

TypeScript använder kontrollflödesanalys för att förstå hur typen av en variabel förändras inom olika kodgrenar. När en type guard används uppdaterar TypeScript sin interna förståelse av variabelns typ, vilket gör att du säkert kan använda metoder och egenskaper som är specifika för den typen.

Exempel på typinskränkning

function processValue(value: string | number | null) {
  if (value === null) {
    console.log("Värdet är null");
  } else if (typeof value === "string") {
    console.log(value.toUpperCase()); // TypeScript vet att 'value' är en sträng här
  } else {
    console.log(value.toFixed(2)); // TypeScript vet att 'value' är ett nummer här
  }
}

processValue("test"); // Output: TEST
processValue(123.456); // Output: 123.46
processValue(null); // Output: Värdet är null

Bästa praxis

För att effektivt utnyttja type guards och type assertions i dina TypeScript-projekt, överväg följande bästa praxis:

Internationella överväganden

När du utvecklar applikationer för en global publik, var medveten om hur type guards och type assertions kan påverka lokaliserings- och internationaliseringsinsatser (i18n). Specifikt, överväg följande:

Slutsats

Type guards och type assertions är väsentliga verktyg för att förbättra typsäkerheten och skriva mer robust TypeScript-kod. Genom att förstå hur man använder dessa funktioner effektivt kan du förhindra körtidsfel, förbättra kodens underhållbarhet och skapa mer tillförlitliga applikationer. Kom ihåg att föredra type guards framför type assertions när det är möjligt, dokumentera dina type assertions och validera extern data för att säkerställa att din typinformation är korrekt. Genom att tillämpa dessa principer kan du skapa mer stabil och förutsägbar mjukvara, lämplig för global distribution.

Bemästra typsäkerhet: En omfattande guide till type guards och type assertions | MLOG