TypeScript har et rikt typesystem, men du trenger ikke forstå alt for å være produktiv. Virkeligheten er at 90% av arbeidet gjøres av en håndfull funksjoner du bruker hver dag. Dette er den praktiske referansen — primitiver, fagforeninger, generics, verktøytyper og innsnevringsmønstrene som får det hele til å klikke. Ingen abstrakt teori; hvert eksempel er den slags kode du skriver i et ekte prosjekt. Hvis du kan JavaScript godt og har gjort noe TypeScript, men vil styrke din mentale modell, er dette for deg. Den fullstendige historien er i TypeScript-håndboken — denne artikkelen er 80/20-versjonen.
Primitiver og literaler
TypeScripts primitive typer mapper direkte til JavaScripts kjøretidstyper:
string, number, boolean, null,
undefined, bigint og symbol. Du bruker de tre første
konstant; resten dukker opp i spesifikke kontekster.
// Primitives — the foundation
let userId: number = 42;
let username: string = 'alice';
let isActive: boolean = true;
let deletedAt: Date | null = null; // nullable pattern
let refreshToken: string | undefined; // optional patternDer TypeScript blir interessant er literaltyper. I stedet for bare
å si at en verdi er en string, kan du si at den må være en av et spesifikt sett med strenger.
Dette er langt mer nyttig enn en enkel string-annotasjon fordi TypeScript kan
fange ugyldige verdier på kompileringstidspunktet:
type Direction = 'north' | 'south' | 'east' | 'west';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type StatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 500;
function navigate(dir: Direction) {
console.log(`Moving ${dir}`);
}
navigate('north'); // ✅
navigate('up'); // ❌ Argument of type '"up"' is not assignable to type 'Direction'
// Combine with string for "known values plus free-form"
type EventName = 'click' | 'focus' | 'blur' | (string & {});(string & {})-trikset til slutt holder autofullfør i gang for
de kjente verdiene mens det fortsatt godtar enhver streng. Det er et vanlig mønster i designsystembiblioteker.interface vs type — når bruker man hva
Dette er spørsmålet enhver TypeScript-nybegynner stiller. Det praktiske svaret: bruk
interface for objektformer — spesielt de som representerer offentlige API-kontrakter eller
som andre typer vil utvide. Bruk type for fagforeninger, skjæringer, kartlagte typer
og alt som ikke utelukkende er en objektform. I praksis er de i stor grad utskiftbare for
objektformer — velg én konvensjon og vær konsekvent innenfor en kodebase.
// interface — object shapes, extensible via extends
interface User {
id: number;
email: string;
createdAt: Date;
}
interface AdminUser extends User {
role: 'admin';
permissions: string[];
}
// type — unions, intersections, aliases for anything
type ID = string | number;
type Nullable<T> = T | null;
// Intersection — combine two shapes (common for mixins / HOC props)
type WithTimestamps = {
createdAt: Date;
updatedAt: Date;
};
type UserRecord = User & WithTimestamps;
// type is required here — interface can't express a union of shapes
type ApiResponse<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string; code: number };En meningsfull forskjell: grensesnitt støtter deklarasjonssammenslåing —
du kan deklarere det samme grensesnittet to ganger og TypeScript slår sammen definisjonene. Slik
utvider biblioteker globale typer (f.eks. å legge til egenskaper til Window). Typer
slås ikke sammen; å re-deklarere en type er en feil.
Fagforenings- og skjæringstyper
Fagforeningstyper (A | B) sier "denne verdien er enten A eller B". Skjæringstyper
(A & B) sier "denne verdien er både A og B på samme tid". Fagforeninger er
overalt i ekte kode — det mest kraftfulle mønsteret de muliggjør er
diskriminert fagforening, som er måten du modellerer data som kan være i forskjellige
tilstander uten å ty til valgfrie felter overalt.
// Discriminated union — model API response states cleanly
type FetchState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: string; retryable: boolean };
// TypeScript narrows the type inside each branch
function render<T>(state: FetchState<T>) {
switch (state.status) {
case 'idle': return '<p>Not started</p>';
case 'loading': return '<p>Loading...</p>';
case 'success': return `<pre>${JSON.stringify(state.data)}</pre>`;
case 'error': return `<p>Error: ${state.error}</p>`;
}
}
// Intersection — common in React/Angular for composing prop types
type ButtonBaseProps = {
label: string;
disabled?: boolean;
};
type IconButtonProps = ButtonBaseProps & {
icon: string;
iconPosition: 'left' | 'right';
};Det diskriminerte fagforenmønsteret er avhengig av en diskriminant — en literal
egenskap (her status) som er unik for hver variant. TypeScript bruker den
egenskapen til å innsnevre den fulle typen inne i betingede grener, noe som gir deg typesikker tilgang til
variantspesifikke felter som data eller error.
Generics — delen som snubler alle
Det motiverende problemet: du vil ha en funksjon som fungerer på flere typer, men bruk av
any kaster bort all typeinformasjon. Generics løser dette — de er typeparametere,
skrevet i vinkelbraketter, som lar en funksjon eller et grensesnitt fungere over en
familie av typer mens full typesikkerhet bevares.
// Without generics — any loses all information
function first(arr: any[]): any {
return arr[0];
}
const x = first([1, 2, 3]); // x is 'any' — TypeScript can't help you here
// With generics — T is inferred from the argument
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first([1, 2, 3]); // n: number | undefined ✅
const s = first(['a', 'b', 'c']); // s: string | undefined ✅Syntaksen <T> deklarerer en typeparameter. Du kan navngi den hva som helst
— T er bare konvensjonen for en enkelt generisk type. Når du kaller funksjonen,
utleder TypeScript T fra argumentene, så du sjelden trenger å skrive det eksplisitt.
Generiske grensesnitt er like nyttige for modellering av API-former:
// Generic interface — the API wrapper pattern every codebase has
interface ApiResponse<T> {
data: T;
meta: {
page: number;
totalPages: number;
totalItems: number;
};
}
interface User {
id: number;
name: string;
email: string;
}
// The response type is fully typed — no casting needed
async function fetchUsers(): Promise<ApiResponse<User[]>> {
const res = await fetch('/api/users');
return res.json();
}
const response = await fetchUsers();
response.data[0].email; // ✅ TypeScript knows this is a string
response.meta.totalPages; // ✅ TypeScript knows this is a number
// Constrained generics — T must have an 'id' field
function findById<T extends { id: number }>(items: T[], id: number): T | undefined {
return items.find(item => item.id === id);
}
const user = findById(users, 42); // T inferred as Useras for å få
returtypen du ønsker. Det er de to tegnene på at en generic ville vært renere.Verktøytyper du faktisk vil bruke
TypeScript leveres med et sett av innebygde verktøytyper som transformerer eksisterende typer til nye. De eliminerer behovet for manuelt å duplisere eller justere typedefinisjoner. Her er de som dukker opp konstant i ekte kodebaser:
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
createdAt: Date;
}
// Partial<T> — all properties become optional
// Perfect for PATCH/update payloads
type UpdateUserPayload = Partial<User>;
// { id?: number; name?: string; email?: string; ... }
async function updateUser(id: number, payload: Partial<User>) {
return fetch(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(payload)
});
}
// Required<T> — all properties become required (opposite of Partial)
interface Config {
apiUrl?: string;
timeout?: number;
retries?: number;
}
type ResolvedConfig = Required<Config>;
// { apiUrl: string; timeout: number; retries: number }
// Pick<T, K> — keep only specified properties
type UserSummary = Pick<User, 'id' | 'name' | 'email'>;
// Use in list views where you only need the display fields
// Omit<T, K> — exclude specified properties
type PublicUser = Omit<User, 'role' | 'createdAt'>;
// Safe to expose in API responses
// Readonly<T> — prevents mutation (great for config and frozen state)
const config: Readonly<ResolvedConfig> = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
};
// config.timeout = 10000; // ❌ Cannot assign to 'timeout' because it is a read-only property
// Record<K, V> — typed dictionary
type UserCache = Record<number, User>;
const cache: UserCache = {};
cache[42] = { id: 42, name: 'Alice', email: '[email protected]', role: 'user', createdAt: new Date() };
// Common pattern: mapping string keys to a known value shape
type FeatureFlags = Record<string, { enabled: boolean; rolloutPct: number }>;
// ReturnType<T> — infer the return type of a function
function createSession(userId: number) {
return {
token: crypto.randomUUID(),
userId,
expiresAt: new Date(Date.now() + 86_400_000)
};
}
type Session = ReturnType<typeof createSession>;
// { token: string; userId: number; expiresAt: Date }
// — no need to define the type separately and keep it in syncunknown vs any vs never
Disse tre typene forvirrer de fleste utviklere til de forstår hvilket problem hver løser.
any er rømningsluken — den deaktiverer typekontroll fullstendig for den
verdien. Det er nyttig ved migrering av JavaScript til TypeScript, men overforbruk ødelegger formålet med
TypeScript. unknown er det typesikre alternativet: verdien kan være hva som helst,
men du må innsnevre den før du kan gjøre noe med den.
never er en type som aldri kan forekomme — bunnen av typehierarkiet.
// any — type checking disabled, use sparingly
function dangerousTransform(input: any) {
return input.toUpperCase(); // TypeScript won't warn even if this crashes at runtime
}
// unknown — safe alternative, forces you to check before using
function safeTransform(input: unknown): string {
if (typeof input === 'string') {
return input.toUpperCase(); // ✅ narrowed to string inside this block
}
if (typeof input === 'number') {
return input.toFixed(2); // ✅ narrowed to number
}
throw new Error(`Cannot transform value of type ${typeof input}`);
}
// Common use: parsing untrusted data (API responses, localStorage, user input)
function parseConfig(raw: unknown): ResolvedConfig {
if (
typeof raw === 'object' &&
raw !== null &&
'apiUrl' in raw &&
typeof (raw as any).apiUrl === 'string'
) {
return raw as ResolvedConfig;
}
throw new Error('Invalid config format');
}
// never — the exhaustive switch pattern
type Shape = { kind: 'circle'; radius: number } | { kind: 'square'; side: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'square': return shape.side ** 2;
default:
// If you add a new Shape variant and forget to handle it here,
// TypeScript will error: Type 'NewShape' is not assignable to type 'never'
const _exhaustive: never = shape;
throw new Error(`Unhandled shape: ${_exhaustive}`);
}
}Typeinnsnevring i praksis
Typeinnsnevring er hvordan TypeScript forfiner en bred type (string | number,
unknown, en diskriminert fagforening) til en spesifikk type inne i en betinget blokk.
TypeScript-innsnevringsdokumentasjonen
dekker hver vakt i dybden — her er mønstrene du skriver daglig.
// typeof — primitive narrowing
function formatValue(val: string | number | boolean): string {
if (typeof val === 'string') return val.trim();
if (typeof val === 'number') return val.toLocaleString();
return val ? 'Yes' : 'No';
// TypeScript knows val must be boolean here
}
// instanceof — class/object narrowing
function handleError(err: unknown): string {
if (err instanceof Error) return err.message;
if (typeof err === 'string') return err;
return 'An unknown error occurred';
}
// in operator — property existence check
type Cat = { meow(): void };
type Dog = { bark(): void };
function makeNoise(animal: Cat | Dog) {
if ('meow' in animal) {
animal.meow(); // TypeScript narrows to Cat
} else {
animal.bark(); // TypeScript narrows to Dog
}
}
// Discriminated union narrowing (most common in component state)
type LoadState<T> =
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string };
function renderState<T>(state: LoadState<T>) {
if (state.status === 'success') {
console.log(state.data); // ✅ data is accessible
} else if (state.status === 'error') {
console.error(state.message); // ✅ message is accessible
}
}
// Type predicates — custom type guards
function isUser(obj: unknown): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj &&
'email' in obj
);
}
// After isUser() returns true, TypeScript knows obj is User
function processPayload(payload: unknown) {
if (isUser(payload)) {
console.log(payload.email); // ✅ fully typed
}
}typeof-operatoren
er det mest grunnleggende innsnevringsverktøyet. Kombinert med
instanceof, in-operatoren og diskriminantegenskapskontroller,
kan du håndtere nesten ethvert innsnevringsscenario uten å ty til any eller usikre
casts.
Oppsummering
TypeScripts typesystem belønner det å lære det grunnleggende godt fremfor å memorere
obskure funksjoner. Literaltyper og diskriminerte fagforeninger eliminerer hele kategorier av kjøretidsfeil.
Generics lar deg skrive gjenbrukbare, typesikre abstraksjoner. Verktøytyper som
Partial, Pick, Omit og ReturnType holder
typene dine DRY. Og unknown med innsnevring gir deg sikkerheten av typer selv ved
kantene der data kommer inn fra omverdenen. Den beste måten å befeste alt dette på er
å eksperimentere interaktivt — TypeScript Playground
lar deg lime inn et vilkårlig kodebiter og se de utledede typene umiddelbart, ingen oppsett nødvendig.
Når du jobber med JSON-data i TypeScript-prosjekter, kan
JSON Formatter og
JSON til TypeScript-verktøyene på dette nettstedet generere
grensesnittdefinisjoner fra ekte API-nyttelaster automatisk.