Norsk

Frigjør kraften i TypeScript-verktøytyper for å skrive renere, mer vedlikeholdbar og typesikker kode. Utforsk praktiske anvendelser med eksempler fra den virkelige verden for utviklere globalt.

Mestring av TypeScript-verktøytyper: En praktisk guide for globale utviklere

TypeScript tilbyr et kraftig sett med innebygde verktøytyper som kan betydelig forbedre kodens typesikkerhet, lesbarhet og vedlikeholdbarhet. Disse verktøytypene er i hovedsak forhåndsdefinerte type-transformasjoner som du kan bruke på eksisterende typer, noe som sparer deg for å skrive repetitiv og feilutsatt kode. Denne guiden vil utforske ulike verktøytyper med praktiske eksempler som appellerer til utviklere over hele verden.

Hvorfor bruke verktøytyper?

Verktøytyper håndterer vanlige scenarier for typemanipulering. Ved å benytte dem kan du:

Kjerneverktøytyper

Partial<T>

Partial<T> konstruerer en type der alle egenskapene til T er satt til valgfrie. Dette er spesielt nyttig når du vil lage en type for delvise oppdateringer eller konfigurasjonsobjekter.

Eksempel:

Se for deg at du bygger en e-handelsplattform med kunder fra ulike regioner. Du har en Customer-type:


interface Customer {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  phoneNumber: string;
  address: {
    street: string;
    city: string;
    country: string;
    postalCode: string;
  };
  preferences?: {
    language: string;
    currency: string;
  }
}

Når du oppdaterer en kundes informasjon, vil du kanskje ikke kreve alle feltene. Partial<Customer> lar deg definere en type der alle egenskapene til Customer er valgfrie:


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... implementasjon for å oppdatere kunden med gitt ID
}

updateCustomer("123", { firstName: "John", lastName: "Doe" }); // Gyldig
updateCustomer("456", { address: { city: "London" } }); // Gyldig

Readonly<T>

Readonly<T> konstruerer en type der alle egenskapene til T er satt til readonly, noe som forhindrer endring etter initialisering. Dette er verdifullt for å sikre uforanderlighet (immutability).

Eksempel:

Tenk deg et konfigurasjonsobjekt for din globale applikasjon:


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // Lagt til versjon
}

const config: AppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

For å forhindre utilsiktet endring av konfigurasjonen etter initialisering, kan du bruke Readonly<AppConfig>:


type ReadonlyAppConfig = Readonly<AppConfig>;

const readonlyConfig: ReadonlyAppConfig = {
  apiUrl: "https://api.example.com",
  theme: "dark",
  supportedLanguages: ["en", "fr", "de", "es", "zh"],
  version: "1.0.0"
};

// readonlyConfig.apiUrl = "https://newapi.example.com"; // Feil: Kan ikke tilordne til 'apiUrl' fordi det er en skrivebeskyttet egenskap.

Pick<T, K>

Pick<T, K> konstruerer en type ved å plukke ut et sett med egenskaper K fra T, der K er en union av streng-litteraltyper som representerer egenskapsnavnene du vil inkludere.

Eksempel:

La oss si at du har et Event-grensesnitt med ulike egenskaper:


interface Event {
  id: string;
  title: string;
  description: string;
  location: string;
  startTime: Date;
  endTime: Date;
  organizer: string;
  attendees: string[];
}

Hvis du bare trenger title, location og startTime for en spesifikk visningskomponent, kan du bruke Pick:


type EventSummary = Pick<Event, "title" | "location" | "startTime">;

function displayEventSummary(event: EventSummary): void {
  console.log(`Event: ${event.title} at ${event.location} on ${event.startTime}`);
}

Omit<T, K>

Omit<T, K> konstruerer en type ved å ekskludere et sett med egenskaper K fra T, der K er en union av streng-litteraltyper som representerer egenskapsnavnene du vil ekskludere. Dette er det motsatte av Pick.

Eksempel:

Ved å bruke det samme Event-grensesnittet, hvis du vil lage en type for å opprette nye arrangementer, vil du kanskje ekskludere id-egenskapen, som vanligvis genereres av backend:


type NewEvent = Omit<Event, "id">;

function createEvent(event: NewEvent): void {
  // ... implementasjon for å opprette et nytt arrangement
}

Record<K, T>

Record<K, T> konstruerer en objekttype der egenskapenes nøkler er K og verdiene er T. K kan være en union av streng-litteraltyper, tall-litteraltyper eller et symbol. Dette er perfekt for å lage ordbøker eller maps.

Eksempel:

Se for deg at du trenger å lagre oversettelser for applikasjonens brukergrensesnitt. Du kan bruke Record for å definere en type for oversettelsene dine:


type Translations = Record<string, string>;

const enTranslations: Translations = {
  "hello": "Hello",
  "goodbye": "Goodbye",
  "welcome": "Welcome to our platform!"
};

const frTranslations: Translations = {
  "hello": "Bonjour",
  "goodbye": "Au revoir",
  "welcome": "Bienvenue sur notre plateforme !"
};

function translate(key: string, language: string): string {
  const translations = language === "en" ? enTranslations : frTranslations; //Forenklet
  return translations[key] || key; // Fallback til nøkkelen hvis ingen oversettelse blir funnet
}

console.log(translate("hello", "en")); // Output: Hello
console.log(translate("hello", "fr")); // Output: Bonjour
console.log(translate("nonexistent", "en")); // Output: nonexistent

Exclude<T, U>

Exclude<T, U> konstruerer en type ved å ekskludere alle union-medlemmer fra T som kan tilordnes til U. Det er nyttig for å filtrere ut spesifikke typer fra en union.

Eksempel:

Du har kanskje en type som representerer forskjellige arrangementstyper:


type EventType = "concert" | "conference" | "workshop" | "webinar";

Hvis du vil lage en type som ekskluderer "webinar"-arrangementer, kan du bruke Exclude:


type PhysicalEvent = Exclude<EventType, "webinar">;

// PhysicalEvent er nå "concert" | "conference" | "workshop"

function attendPhysicalEvent(event: PhysicalEvent): void {
  console.log(`Attending a ${event}`);
}

// attendPhysicalEvent("webinar"); // Feil: Argument av typen '"webinar"' kan ikke tilordnes til parameter av typen '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // Gyldig

Extract<T, U>

Extract<T, U> konstruerer en type ved å trekke ut alle union-medlemmer fra T som kan tilordnes til U. Dette er det motsatte av Exclude.

Eksempel:

Ved å bruke samme EventType, kan du trekke ut webinar-arrangementstypen:


type OnlineEvent = Extract<EventType, "webinar">;

// OnlineEvent er nå "webinar"

function attendOnlineEvent(event: OnlineEvent): void {
  console.log(`Attending a ${event} online`);
}

attendOnlineEvent("webinar"); // Gyldig
// attendOnlineEvent("concert"); // Feil: Argument av typen '"concert"' kan ikke tilordnes til parameter av typen '"webinar"'.

NonNullable<T>

NonNullable<T> konstruerer en type ved å ekskludere null og undefined fra T.

Eksempel:


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString er nå string

function processString(str: DefinitelyString): void {
  console.log(str.toUpperCase());
}

// processString(null); // Feil: Argument av typen 'null' kan ikke tilordnes til parameter av typen 'string'.
// processString(undefined); // Feil: Argument av typen 'undefined' kan ikke tilordnes til parameter av typen 'string'.
processString("hello"); // Gyldig

ReturnType<T>

ReturnType<T> konstruerer en type som består av returtypen til funksjonen T.

Eksempel:


function greet(name: string): string {
  return `Hello, ${name}!`;
}

type Greeting = ReturnType<typeof greet>;

// Greeting er nå string

const message: Greeting = greet("World");

console.log(message);

Parameters<T>

Parameters<T> konstruerer en tuppeltype fra typene til parameterne i en funksjonstype T.

Eksempel:


function logEvent(eventName: string, eventData: object): void {
  console.log(`Event: ${eventName}`, eventData);
}

type LogEventParams = Parameters<typeof logEvent>;

// LogEventParams er nå [eventName: string, eventData: object]

const params: LogEventParams = ["user_login", { userId: "123", timestamp: Date.now() }];

logEvent(...params);

ConstructorParameters<T>

ConstructorParameters<T> konstruerer en tuppel- eller array-type fra typene til parameterne i en konstruktørfunksjonstype T. Den utleder typene til argumentene som må sendes til konstruktøren til en klasse.

Eksempel:


class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}


type GreeterParams = ConstructorParameters<typeof Greeter>;

// GreeterParams er nå [message: string]

const paramsGreeter: GreeterParams = ["World"];
const greeterInstance = new Greeter(...paramsGreeter);

console.log(greeterInstance.greet()); // Gir utdata: Hello, World

Required<T>

Required<T> konstruerer en type som består av alle egenskapene til T satt til påkrevd. Den gjør alle valgfrie egenskaper påkrevde.

Eksempel:


interface UserProfile {
  name: string;
  age?: number;
  email?: string;
}

type RequiredUserProfile = Required<UserProfile>;

// RequiredUserProfile er nå { name: string; age: number; email: string; }

const completeProfile: RequiredUserProfile = {
  name: "Alice",
  age: 30,
  email: "alice@example.com"
};

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Feil: Egenskapen 'age' mangler i typen '{ name: string; }', men er påkrevd i typen 'Required'.

Avanserte verktøytyper

Mal-litteraltyper

Mal-litteraltyper lar deg konstruere nye streng-litteraltyper ved å kjede sammen eksisterende streng-litteraltyper, tall-litteraltyper og mer. Dette muliggjør kraftig strengbasert typemanipulering.

Eksempel:


type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/users` | `/api/products`;

type RequestURL = `${HTTPMethod} ${APIEndpoint}`;

// RequestURL er nå "GET /api/users" | "POST /api/users" | "PUT /api/users" | "DELETE /api/users" | "GET /api/products" | "POST /api/products" | "PUT /api/products" | "DELETE /api/products"

function makeRequest(url: RequestURL): void {
  console.log(`Making request to ${url}`);
}

makeRequest("GET /api/users"); // Gyldig
// makeRequest("INVALID /api/users"); // Feil

Betingede typer

Betingede typer lar deg definere typer som avhenger av en betingelse uttrykt som et typeforhold. De bruker nøkkelordet infer for å trekke ut typeinformasjon.

Eksempel:


type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

// Hvis T er en Promise, er typen U; ellers er typen T.

async function fetchData(): Promise<number> {
  return 42;
}


type Data = UnwrapPromise<ReturnType<typeof fetchData>>;

// Data er nå number

function processData(data: Data): void {
  console.log(data * 2);
}

processData(await fetchData());

Praktiske anvendelser og scenarier fra den virkelige verden

La oss utforske mer komplekse scenarier fra den virkelige verden der verktøytyper virkelig skinner.

1. Skjemahåndtering

Når du jobber med skjemaer, har du ofte scenarier der du trenger å representere de opprinnelige skjemeverdiene, de oppdaterte skjemeverdiene og de endelige innsendte verdiene. Verktøytyper kan hjelpe deg med å håndtere disse ulike tilstandene effektivt.


interface FormData {
  firstName: string;
  lastName: string;
  email: string;
  country: string; // Påkrevd
  city?: string; // Valgfri
  postalCode?: string;
  newsletterSubscription?: boolean;
}

// Opprinnelige skjemeverdier (valgfrie felt)
type InitialFormValues = Partial<FormData>;

// Oppdaterte skjemeverdier (noen felt kan mangle)
type UpdatedFormValues = Partial<FormData>;

// Påkrevde felt for innsending
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// Bruk disse typene i skjemakomponentene dine
function initializeForm(initialValues: InitialFormValues): void { }
function updateForm(updates: UpdatedFormValues): void {}
function submitForm(data: RequiredForSubmission): void {}


const initialForm: InitialFormValues = { newsletterSubscription: true };

const updateFormValues: UpdatedFormValues = {
    firstName: "John",
    lastName: "Doe"
};

// const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test" }; // FEIL: Mangler 'country' 
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; //OK


2. Transformasjon av API-data

Når du henter data fra et API, kan det hende du må transformere dataene til et annet format for applikasjonen din. Verktøytyper kan hjelpe deg med å definere strukturen til de transformerte dataene.


interface APIResponse {
  user_id: string;
  first_name: string;
  last_name: string;
  email_address: string;
  profile_picture_url: string;
  is_active: boolean;
}

// Transformer API-responsen til et mer lesbart format
type UserData = {
  id: string;
  fullName: string;
  email: string;
  avatar: string;
  active: boolean;
};

function transformApiResponse(response: APIResponse): UserData {
  return {
    id: response.user_id,
    fullName: `${response.first_name} ${response.last_name}`,
    email: response.email_address,
    avatar: response.profile_picture_url,
    active: response.is_active
  };
}

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


// Du kan til og med håndheve typen ved å:

function saferTransformApiResponse(response: APIResponse): UserData {
    const {user_id, first_name, last_name, email_address, profile_picture_url, is_active} = response;
    const transformed: UserData = {
        id: user_id,
        fullName: `${first_name} ${last_name}`,
        email: email_address,
        avatar: profile_picture_url,
        active: is_active
    };

    return transformed;
}

3. Håndtering av konfigurasjonsobjekter

Konfigurasjonsobjekter er vanlige i mange applikasjoner. Verktøytyper kan hjelpe deg med å definere strukturen til konfigurasjonsobjektet og sikre at det brukes riktig.


interface AppSettings {
  theme: "light" | "dark";
  language: string;
  notificationsEnabled: boolean;
  apiUrl?: string; // Valgfri API-URL for ulike miljøer
  timeout?: number;  //Valgfri
}

// Standardinnstillinger
const defaultSettings: AppSettings = {
  theme: "light",
  language: "en",
  notificationsEnabled: true
};

// Funksjon for å slå sammen brukerinnstillinger med standardinnstillinger
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// Bruk de sammenslåtte innstillingene i applikasjonen din
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

Tips for effektiv bruk av verktøytyper

  • Start enkelt: Begynn med grunnleggende verktøytyper som Partial og Readonly før du går videre til mer komplekse.
  • Bruk beskrivende navn: Gi type-aliasene dine meningsfulle navn for å forbedre lesbarheten.
  • Kombiner verktøytyper: Du kan kombinere flere verktøytyper for å oppnå komplekse type-transformasjoner.
  • Utnytt editor-støtte: Dra nytte av den utmerkede editor-støtten i TypeScript for å utforske effektene av verktøytyper.
  • Forstå de underliggende konseptene: En solid forståelse av TypeScripts typesystem er avgjørende for effektiv bruk av verktøytyper.

Konklusjon

TypeScript-verktøytyper er kraftige verktøy som kan betydelig forbedre kvaliteten og vedlikeholdbarheten til koden din. Ved å forstå og anvende disse verktøytypene effektivt, kan du skrive renere, mer typesikre og mer robuste applikasjoner som møter kravene i et globalt utviklingslandskap. Denne guiden har gitt en omfattende oversikt over vanlige verktøytyper og praktiske eksempler. Eksperimenter med dem og utforsk deres potensial for å forbedre dine TypeScript-prosjekter. Husk å prioritere lesbarhet og klarhet når du bruker verktøytyper, og streb alltid etter å skrive kode som er lett å forstå og vedlikeholde, uansett hvor dine medutviklere befinner seg.