Norsk

Mestre TypeScript sine utility-typer: kraftige verktøy for typetransformasjoner, som forbedrer gjenbruk av kode og øker typesikkerheten i applikasjonene dine.

TypeScript Utility-typer: Innebygde verktøy for typemanipulering

TypeScript er et kraftig språk som bringer statisk typing til JavaScript. En av nøkkelfunksjonene er evnen til å manipulere typer, noe som lar utviklere lage mer robust og vedlikeholdbar kode. TypeScript tilbyr et sett med innebygde utility-typer som forenkler vanlige typetransformasjoner. Disse utility-typene er uvurderlige verktøy for å forbedre typesikkerhet, øke gjenbruk av kode og effektivisere utviklingsprosessen. Denne omfattende guiden utforsker de mest essensielle TypeScript utility-typene, og gir praktiske eksempler og innsikt for å hjelpe deg med å mestre dem.

Hva er TypeScript Utility-typer?

Utility-typer er forhåndsdefinerte typeoperatorer som transformerer eksisterende typer til nye typer. De er innebygd i TypeScript-språket og gir en konsis og deklarativ måte å utføre vanlige typemanipuleringer på. Bruk av utility-typer kan redusere mengden standardkode betraktelig og gjøre typedefinisjonene dine mer uttrykksfulle og lettere å forstå.

Tenk på dem som funksjoner som opererer på typer i stedet for verdier. De tar en type som input og returnerer en modifisert type som output. Dette lar deg lage komplekse typeforhold og transformasjoner med minimal kode.

Hvorfor bruke Utility-typer?

Det er flere gode grunner til å inkludere utility-typer i TypeScript-prosjektene dine:

Essensielle TypeScript Utility-typer

La oss utforske noen av de mest brukte og nyttige utility-typene i TypeScript. Vi vil dekke deres formål, syntaks og gi praktiske eksempler for å illustrere bruken deres.

1. Partial<T>

Utility-typen Partial<T> gjør alle egenskapene til typen T valgfrie. Dette er nyttig når du vil lage en ny type som har noen eller alle egenskapene til en eksisterende type, men du ikke vil kreve at alle er til stede.

Syntaks:

type Partial<T> = { [P in keyof T]?: T[P]; };

Eksempel:

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

type OptionalUser = Partial<User>; // Alle egenskaper er nå valgfrie

const partialUser: OptionalUser = {
 name: "Alice", // Oppgir kun 'name'-egenskapen
};

Bruksområde: Oppdatering av et objekt med kun visse egenskaper. Forestill deg for eksempel et skjema for oppdatering av brukerprofil. Du vil ikke kreve at brukerne oppdaterer hvert felt samtidig.

2. Required<T>

Utility-typen Required<T> gjør alle egenskapene til typen T påkrevde. Det er det motsatte av Partial<T>. Dette er nyttig når du har en type med valgfrie egenskaper, og du vil sikre at alle egenskaper er til stede.

Syntaks:

type Required<T> = { [P in keyof T]-?: T[P]; };

Eksempel:

interface Config {
 apiKey?: string;
 apiUrl?: string;
}

type CompleteConfig = Required<Config>; // Alle egenskaper er nå påkrevde

const config: CompleteConfig = {
 apiKey: "your-api-key",
 apiUrl: "https://example.com/api",
};

Bruksområde: Håndheve at alle konfigurasjonsinnstillinger er gitt før en applikasjon starter. Dette kan bidra til å forhindre kjøretidsfeil forårsaket av manglende eller udefinerte innstillinger.

3. Readonly<T>

Utility-typen Readonly<T> gjør alle egenskapene til typen T skrivebeskyttet. Dette forhindrer deg i å endre egenskapene til et objekt ved et uhell etter at det er opprettet. Dette fremmer uforanderlighet (immutability) og forbedrer forutsigbarheten til koden din.

Syntaks:

type Readonly<T> = { readonly [P in keyof T]: T[P]; };

Eksempel:

interface Product {
 id: number;
 name: string;
 price: number;
}

type ImmutableProduct = Readonly<Product>; // Alle egenskaper er nå skrivebeskyttet

const product: ImmutableProduct = {
 id: 123,
 name: "Eksempelprodukt",
 price: 25.99,
};

// product.price = 29.99; // Feil: Kan ikke tilordne til 'price' fordi det er en skrivebeskyttet egenskap.

Bruksområde: Opprette uforanderlige datastrukturer, som konfigurasjonsobjekter eller dataoverføringsobjekter (DTOs), som ikke skal endres etter opprettelse. Dette er spesielt nyttig i funksjonelle programmeringsparadigmer.

4. Pick<T, K extends keyof T>

Utility-typen Pick<T, K extends keyof T> lager en ny type ved å plukke ut et sett med egenskaper K fra typen T. Dette er nyttig når du bare trenger et delsett av egenskapene til en eksisterende type.

Syntaks:

type Pick<T, K extends keyof T> = { [P in K]: T[P]; };

Eksempel:

interface Employee {
 id: number;
 name: string;
 department: string;
salary: number;
}

type EmployeeNameAndDepartment = Pick<Employee, "name" | "department">; // Plukker kun ut 'name' og 'department'

const employeeInfo: EmployeeNameAndDepartment = {
 name: "Bob",
 department: "Engineering",
};

Bruksområde: Lage spesialiserte dataoverføringsobjekter (DTOs) som bare inneholder nødvendige data for en bestemt operasjon. Dette kan forbedre ytelsen og redusere datamengden som overføres over nettverket. Tenk deg å sende brukerdetaljer til klienten, men ekskludere sensitiv informasjon som lønn. Du kan bruke Pick til å bare sende `id` og `name`.

5. Omit<T, K extends keyof any>

Utility-typen Omit<T, K extends keyof any> lager en ny type ved å utelate et sett med egenskaper K fra typen T. Dette er det motsatte av Pick<T, K extends keyof T> og er nyttig når du vil ekskludere visse egenskaper fra en eksisterende type.

Syntaks:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Eksempel:

interface Event {
 id: number;
 title: string;
description: string;
 date: Date;
 location: string;
}

type EventSummary = Omit<Event, "description" | "location">; // Utelater 'description' og 'location'

const eventPreview: EventSummary = {
 id: 1,
 title: "Konferanse",
 date: new Date(),
};

Bruksområde: Lage forenklede versjoner av datamodeller for spesifikke formål, for eksempel å vise et sammendrag av et arrangement uten å inkludere full beskrivelse og sted. Dette kan også brukes til å fjerne sensitive felt før data sendes til en klient.

6. Exclude<T, U>

Utility-typen Exclude<T, U> lager en ny type ved å ekskludere alle typer fra T som kan tilordnes U. Dette er nyttig når du vil fjerne visse typer fra en union-type.

Syntaks:

type Exclude<T, U> = T extends U ? never : T;

Eksempel:

type AllowedFileTypes = "image" | "video" | "audio" | "document";
type MediaFileTypes = "image" | "video" | "audio";

type DocumentFileTypes = Exclude<AllowedFileTypes, MediaFileTypes>; // "document"

const fileType: DocumentFileTypes = "document";

Bruksområde: Filtrere en union-type for å fjerne spesifikke typer som ikke er relevante i en bestemt kontekst. For eksempel kan det være lurt å ekskludere visse filtyper fra en liste over tillatte filtyper.

7. Extract<T, U>

Utility-typen Extract<T, U> lager en ny type ved å trekke ut alle typer fra T som kan tilordnes U. Dette er det motsatte av Exclude<T, U> og er nyttig når du vil velge ut spesifikke typer fra en union-type.

Syntaks:

type Extract<T, U> = T extends U ? T : never;

Eksempel:

type InputTypes = string | number | boolean | null | undefined;
type PrimitiveTypes = string | number | boolean;

type NonNullablePrimitives = Extract<InputTypes, PrimitiveTypes>; // string | number | boolean

const value: NonNullablePrimitives = "hello";

Bruksområde: Velge ut spesifikke typer fra en union-type basert på visse kriterier. For eksempel kan det være lurt å trekke ut alle primitive typer fra en union-type som inkluderer både primitive typer og objekttyper.

8. NonNullable<T>

Utility-typen NonNullable<T> lager en ny type ved å ekskludere null og undefined fra typen T. Dette er nyttig når du vil sikre at en type ikke kan være null eller undefined.

Syntaks:

type NonNullable<T> = T extends null | undefined ? never : T;

Eksempel:

type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>; // string

const message: DefinitelyString = "Hello, world!";

Bruksområde: Håndheve at en verdi ikke er null eller undefined før du utfører en operasjon på den. Dette kan bidra til å forhindre kjøretidsfeil forårsaket av uventede null- eller undefined-verdier. Tenk på et scenario der du må behandle en brukers adresse, og det er avgjørende at adressen ikke er null før noen operasjon.

9. ReturnType<T extends (...args: any) => any>

Utility-typen ReturnType<T extends (...args: any) => any> trekker ut returtypen til en funksjonstype T. Dette er nyttig når du vil vite typen til verdien som en funksjon returnerer.

Syntaks:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

Eksempel:

function fetchData(url: string): Promise<{ data: any }> {
 return fetch(url).then(response => response.json());
}

type FetchDataReturnType = ReturnType<typeof fetchData>; // Promise<{ data: any }>

async function processData(data: FetchDataReturnType) {
 // ...
}

Bruksområde: Bestemme typen til verdien som returneres av en funksjon, spesielt når man håndterer asynkrone operasjoner eller komplekse funksjonssignaturer. Dette lar deg sikre at du håndterer den returnerte verdien korrekt.

10. Parameters<T extends (...args: any) => any>

Utility-typen Parameters<T extends (...args: any) => any> trekker ut parametertypene til en funksjonstype T som en tuppel. Dette er nyttig når du vil vite typene til argumentene som en funksjon aksepterer.

Syntaks:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

Eksempel:

function createUser(name: string, age: number, email: string): void {
 // ...
}

type CreateUserParams = Parameters<typeof createUser>; // [string, number, string]

function logUser(...args: CreateUserParams) {
 console.log("Oppretter bruker med:", args);
}

Bruksområde: Bestemme typene til argumentene som en funksjon aksepterer, noe som kan være nyttig for å lage generiske funksjoner eller dekoratorer som må fungere med funksjoner med forskjellige signaturer. Det hjelper med å sikre typesikkerhet når argumenter sendes dynamisk til en funksjon.

11. ConstructorParameters<T extends abstract new (...args: any) => any>

Utility-typen ConstructorParameters<T extends abstract new (...args: any) => any> trekker ut parametertypene til en konstruktørfunksjonstype T som en tuppel. Dette er nyttig når du vil vite typene til argumentene som en konstruktør aksepterer.

Syntaks:

type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never;

Eksempel:

class Logger {
 constructor(public prefix: string, public enabled: boolean) {}
 log(message: string) {
 if (this.enabled) {
 console.log(`${this.prefix}: ${message}`);
 }
 }
}

type LoggerConstructorParams = ConstructorParameters<typeof Logger>; // [string, boolean]

function createLogger(...args: LoggerConstructorParams) {
 return new Logger(...args);
}

Bruksområde: Ligner på Parameters, men spesifikt for konstruktørfunksjoner. Det hjelper når man lager fabrikker (factories) eller systemer for avhengighetsinjeksjon (dependency injection) der man må instansiere klasser dynamisk med forskjellige konstruktørsignaturer.

12. InstanceType<T extends abstract new (...args: any) => any>

Utility-typen InstanceType<T extends abstract new (...args: any) => any> trekker ut instanstypen til en konstruktørfunksjonstype T. Dette er nyttig når du vil vite typen til objektet som en konstruktør lager.

Syntaks:

type InstanceType<T extends abstract new (...args: any) => any> = T extends abstract new (...args: any) => infer R ? R : any;

Eksempel:

class Greeter {
 greeting: string;
 constructor(message: string) {
 this.greeting = message;
 }
 greet() {
 return "Hello, " + this.greeting;
 }
}

type GreeterInstance = InstanceType<typeof Greeter>; // Greeter

const myGreeter: GreeterInstance = new Greeter("World");
console.log(myGreeter.greet());

Bruksområde: Bestemme typen til objektet som er opprettet av en konstruktør, noe som er nyttig når man jobber med arv eller polymorfisme. Det gir en typesikker måte å referere til instansen av en klasse.

13. Record<K extends keyof any, T>

Utility-typen Record<K extends keyof any, T> konstruerer en objekttype der egenskapenes nøkler er K og deres verdier er T. Dette er nyttig for å lage ordboklignende typer der du kjenner nøklene på forhånd.

Syntaks:

type Record<K extends keyof any, T> = { [P in K]: T; };

Eksempel:

type CountryCode = "US" | "CA" | "GB" | "DE";

type CurrencyMap = Record<CountryCode, string>; // { US: string; CA: string; GB: string; DE: string; }

const currencies: CurrencyMap = {
 US: "USD",
 CA: "CAD",
 GB: "GBP",
 DE: "EUR",
};

Bruksområde: Lage ordboklignende objekter der du har et fast sett med nøkler og vil sikre at alle nøkler har verdier av en bestemt type. Dette er vanlig når man jobber med konfigurasjonsfiler, datakartlegginger eller oppslagstabeller.

Egendefinerte Utility-typer

Selv om TypeScript sine innebygde utility-typer er kraftige, kan du også lage dine egne tilpassede utility-typer for å dekke spesifikke behov i prosjektene dine. Dette lar deg kapsle inn komplekse typetransformasjoner og gjenbruke dem i hele kodebasen din.

Eksempel:

// En utility-type for å hente nøklene til et objekt som har en spesifikk type
type KeysOfType<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];

interface Person {
 name: string;
 age: number;
 address: string;
 phoneNumber: number;
}

type StringKeys = KeysOfType<Person, string>; // "name" | "address"

Beste praksis for bruk av Utility-typer

Konklusjon

TypeScript utility-typer er kraftige verktøy som kan forbedre typesikkerheten, gjenbrukbarheten og vedlikeholdbarheten til koden din betydelig. Ved å mestre disse utility-typene kan du skrive mer robuste og uttrykksfulle TypeScript-applikasjoner. Denne guiden har dekket de mest essensielle TypeScript utility-typene, og gitt praktiske eksempler og innsikt for å hjelpe deg med å innlemme dem i prosjektene dine.

Husk å eksperimentere med disse utility-typene og utforske hvordan de kan brukes til å løse spesifikke problemer i din egen kode. Etter hvert som du blir mer kjent med dem, vil du finne deg selv i å bruke dem mer og mer for å lage renere, mer vedlikeholdbare og mer typesikre TypeScript-applikasjoner. Enten du bygger webapplikasjoner, server-side applikasjoner eller noe i mellom, gir utility-typer et verdifullt sett med verktøy for å forbedre utviklingsprosessen og kvaliteten på koden din. Ved å utnytte disse innebygde verktøyene for typemanipulering, kan du låse opp det fulle potensialet til TypeScript og skrive kode som er både uttrykksfull og robust.