Link Search Menu Expand Document (external link)

AJ 1.4 Typningssystem för JavaScript

Table of contents


TypeScript

TypeScript är ett superset JavaScript, som är typat. Det kompileras sen till vanlig JS. Att det är typat innebär att vi definierar vilken typ en variabel eller parameter har, eller vilken typ en funktion ska returnera.

I vanlig JS så definieras inte typer, därför kan också en variabel byta typ. Vi kan inte kontrollera typning av värden, vare sig vid deklarering eller tilldelning. Typningen i TypeScript är frivillig, du kan ha det mer eller mindre strict. I tsconfig kan vi ställa in hur lite typechecking som ska göras.

Varför typa?

  • Extra säkerhet
  • Underlättar bugg-letande
  • Underlättar vid refaktorisering
  • Dokumentation (framtida programmerare kan se vilken typ som var ämnad i din kod)

Använda TypeScript

Implicit vs explicit typning

I TypeScript går det att typa implicit eller explicit:

Implicit typning

Vi säger inte explicit att variabeln number ska ha typen number, men eftersom vi tilldelar den ett värde som är ett nummer när vi deklarerar den, så kommer TypeScript att typa den som ett nummer.

let value = 123;
value = '456'; // error: cannot assign 'string' to 'number'

Explicit typning

Här typar vi variabeln explicit, alltså säger till TypeScript att den här variabeln är ett nummer.

let value: number = 123;
let otherValue: number = '456'; // error: cannot assign 'string' to 'number'

Duck typing och strukturella typer

TypeScripts typer är strukturella, innebär att det använder duck typing. För att kolla vilken typ något är, så kollas vilka egenskaper objektet har, snarare än objektet i sig (vilket kan vara fallet i andra typade språk).

Exempel

Här har vi skapat en egen typ som heter Position, som har egenskaperna x och y.

interface Position {
  x: number;
  y: number;
}

Det andra objektet saknar egenskapen y.

const first: Position = { x: 0, y: 5 }
const second: Position = { x: 10 }

När vi sen definierar en funktion som tar in en parameter av typen Position, så kommer den inte att fungera att kalla med det andra objektet ovan, pga en egenskap saknas (y).

function handleStuff(position: Position) {
  // ...
}

handleStuff(first); // ok
handleStuff(second); // error

Olika typer

I TypeScript denoterar vi typer med :type (alltså säger vilken typ något ska vara). Alla primitiva typer i JS finns i TS också. Det går också att typa att vi vill ha en array med en viss typ.

Exempel

const value: number = 123;
const name: string = 'Bosse';
const yes: boolean = true;
const valueArray: number[];

Vi kan definiera egna typer via ett interface. Här kan vi också säga att vissa egenskaper bara ska gå att sätta när vi definierar ett nytt objekt av vår typ, och inte gå att ändra senare, med ordet readonly.

Exempel

interface MyCustomType {
  readonly name: string;
  age: number;
  favouriteFood: string[];
}

let me: MyCustomType = {
  name: 'Karin',
  age: 35,
  favouriteFood: [
    'pizza', 'pasta', 'salad'
  ]
}

me.name = 'Hanna'; //error

Det går också att kombinera två typer, s.k. intersection. Till exempel kan vi kombinera två interfaces till en ny typ. Denoteras med &. Den nya typen får då bägge de två typernas egenskaper.

Exempel

interface Worker {
  id: number;
  name: string;
}

interface Company {
  companyName: string;
  city: string;
}

type Consultant = Worker & Company;

// new type 'Consultant' has all properties from 'Worker' and 'Company'
let consultant: Consultant;
consultant.id = 123456;
consultant.name = 'Bosse';
consultant.companyName = 'Chas Academy';
consultant.city = 'Stockholm';

TS har också speciella typer, som any och void. any kan vara vilken typ som helst, och void kan indikera att ingenting kommer att returneras från en funktion eller metod.

Exempel

any

let value: any;

value = 123; // ok
value = '123'; // ok

void

const doSomething = (parameter): void => {
  // do something
  // but don't return anything
}

Med any så förlorar vi ju kontrollen, eftersom det kan betyda vad som helst. Vi kan då istället använda generics, där vi sätter en typ som sen kan ändras, t.ex i funktioner som ska fungera med flera olika typer.

Exempel

Här vet vi varken vilken typ parametern har eller vilken typ som returneras.

const genericFn = <any>(thing: any): any => {
  return thing;
}

Vi ändrar istället funktionen så att den är av generisk typ T, den tar T som parameter och returnerar T. När vi sen kallar funktionen som anger vi vilken typ T ska vara.

function genericFn<T>(thing: T): T {
  return thing;
}

// generic type T turns into type string
genericFn<string>('Hello World!');

Angående generics i arrow functions!

Använder vi generics i arrow functions i React (med .tsx-filer, eller om vi inte har förbjudit JSX i våra .ts-filer), så kommer <T> läsas som JSX, och vi kommer att få error pga ingen slut-tag.

// error
const genericFn = <T>(thing: T): T => {
  return thing;
}

Det finns dock lite hacks för att komma runt det. Ett sätt är att specificera att T extendar unknown (eller {}, funkar också med any men det rekommenderas inte), vi talar då om för compilern att detta är en generic.1

const genericFn = <T extends unknown>(thing: T): T => {
  return thing;
}

Ett annat sätt är att sätta ett trailing komma efter T, så lurar vi compilern att vi har två parametrar, fastän vi bara har en.

const genericFn = <T,>(thing: T): T => {
  return thing;
}

Typen union tillåter en kombination av en eller flera typer, alltså att en parameter kan vara av olika typer, eller att en funktion kan returnera olika typer.

Exempel

const string: number | string;

Har vi satt en union som i exemplet ovan så kan vi inte använda metoder som t.ex bara finns i number eller bara i string, utan vi kan bara använda metoder som finns i båda.

En tuple är en typ där vi kan sätta ihop flera specifika typer till en samlad struktur. Det är ingen egen typ i sig, utan att vi i t.ex. en array specificerar vilka typer vi vill ha på värdena i arrayen.2

Exempel

En array där de två första värdena måste vara strängar, och de två andra nummer.

const stringNumberArray: [string, string, number, number];

Ett alias är en placeholder som vi kan definiera med flera olika typer, och sen använda på flera ställen.

Exempel

// declare alias types
type StringOrNum = string | number;
type Coordinates = [number, number];
type text = string | { text: string };

// use alias type
let thing: StringOrNum;

// value can be set to both number and string
thing = 123; // ok
thing = '123'; // ok

Kompilering

TypeScript fel som uppstår pga typning kommer ändå att kompileras till fungerande JS.

I TS projekt brukar det det finnas en tsconfig.json, som beskriver hur kompilatorn ska bete sig.

Installera kompilatorn

$ npm install -g typescript

Om vi sen har en TypeScript-fil, t.ex. hello.ts, så kan vi kompilera den i terminalen

$ tsc hello.ts

Får vi errors så loggas dem nu, annars så får vi en hello.js med vår kod i filen ovan i ren JavaScript.


Migrering

Det går ganska enkelt att uppgradera ett JavaScript-projekt till ett TypeScript-projekt, även när utvecklingen kommit långt. Du laddar ner TypeScript till projektet och döper sedan om filerna .js/.jsx till .ts/.tsx. Rekommenderat att ställa in den på väldigt strikt läge i tsconfig.json tidigt.

Lyckas du inte övertala resten av teamet att det vore nice att migrera till TS? Microsofts TypeScript-blogg har ett inlägg om hur du gör det utan att nån märker det… 3


Mer läsning


Referenser