Dansk

Lås op for kraften i TypeScript utility typer for at skrive renere, mere vedligeholdelig og typesikker kode. Udforsk praktiske anvendelser med eksempler fra den virkelige verden for udviklere over hele verden.

Mestring af TypeScript Utility Typer: En Praktisk Guide til Globale Udviklere

TypeScript tilbyder et kraftfuldt sæt indbyggede utility typer, der markant kan forbedre din kodes typesikkerhed, læsbarhed og vedligeholdelighed. Disse utility typer er i bund og grund prædefinerede type-transformationer, som du kan anvende på eksisterende typer, hvilket sparer dig for at skrive gentagende og fejlbehæftet kode. Denne guide vil udforske forskellige utility typer med praktiske eksempler, der resonerer med udviklere over hele kloden.

Hvorfor Bruge Utility Typer?

Utility typer adresserer almindelige scenarier for typemanipulation. Ved at udnytte dem kan du:

Centrale Utility Typer

Partial

Partial konstruerer en type, hvor alle egenskaber i T er indstillet til valgfri. Dette er især nyttigt, når du vil oprette en type til delvise opdateringer eller konfigurationsobjekter.

Eksempel:

Forestil dig, at du bygger en e-handelsplatform med kunder fra forskellige 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 opdaterer en kundes oplysninger, ønsker du måske ikke at kræve alle felter. Partial giver dig mulighed for at definere en type, hvor alle egenskaber i Customer er valgfrie:


type PartialCustomer = Partial<Customer>;

function updateCustomer(id: string, updates: PartialCustomer): void {
  // ... implementering for at opdatere kunden med det givne ID
}

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

Readonly

Readonly konstruerer en type, hvor alle egenskaber i T er indstillet til readonly, hvilket forhindrer ændring efter initialisering. Dette er værdifuldt for at sikre uforanderlighed.

Eksempel:

Overvej et konfigurationsobjekt til din globale applikation:


interface AppConfig {
  apiUrl: string;
  theme: string;
  supportedLanguages: string[];
  version: string; // Tilføjet version
}

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

For at forhindre utilsigtet ændring af konfigurationen efter initialisering, kan du bruge Readonly:


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"; // Fejl: Kan ikke tildele til 'apiUrl', fordi det er en skrivebeskyttet egenskab.

Pick

Pick konstruerer en type ved at vælge sættet af egenskaber K fra T, hvor K er en union af strengliteraltyper, der repræsenterer de egenskabsnavne, du vil inkludere.

Eksempel:

Lad os sige, at du har en Event interface med forskellige egenskaber:


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

Hvis du kun har brug for title, location og startTime til en specifik displaykomponent, kan du bruge Pick:


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

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

Omit

Omit konstruerer en type ved at udelade sættet af egenskaber K fra T, hvor K er en union af strengliteraltyper, der repræsenterer de egenskabsnavne, du vil udelade. Dette er det modsatte af Pick.

Eksempel:

Ved hjælp af den samme Event interface, hvis du vil oprette en type til oprettelse af nye begivenheder, vil du måske udelade id egenskaben, som typisk genereres af backend:


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

function createEvent(event: NewEvent): void {
  // ... implementering for at oprette en ny begivenhed
}

Record

Record konstruerer en objekttype, hvis egenskabsnøgler er K, og hvis egenskabsværdier er T. K kan være en union af strengliteraltyper, talliteraltyper eller et symbol. Dette er perfekt til at oprette ordbøger eller kort.

Eksempel:

Forestil dig, at du skal gemme oversættelser til din applikations brugergrænseflade. Du kan bruge Record til at definere en type til dine oversættelser:


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; //Simplified
  return translations[key] || key; // Fallback to the key if no translation is found
}

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

Exclude

Exclude konstruerer en type ved at udelukke fra T alle unionmedlemmer, der kan tildeles U. Det er nyttigt til at filtrere specifikke typer fra en union.

Eksempel:

Du har måske en type, der repræsenterer forskellige begivenhedstyper:


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

Hvis du vil oprette en type, der udelukker "webinar" begivenheder, kan du bruge Exclude:


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

// PhysicalEvent er nu "concert" | "conference" | "workshop"

function attendPhysicalEvent(event: PhysicalEvent): void {
  console.log(`Deltager i en ${event}`);
}

// attendPhysicalEvent("webinar"); // Fejl: Argument af typen '"webinar"' kan ikke tildeles parameteren af typen '"concert" | "conference" | "workshop"'.

attendPhysicalEvent("concert"); // Gyldigt

Extract

Extract konstruerer en type ved at udtrække fra T alle unionmedlemmer, der kan tildeles U. Dette er det modsatte af Exclude.

Eksempel:

Ved hjælp af den samme EventType, kan du udtrække webinar-begivenhedstypen:


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

// OnlineEvent er nu "webinar"

function attendOnlineEvent(event: OnlineEvent): void {
  console.log(`Deltager i en ${event} online`);
}

attendOnlineEvent("webinar"); // Gyldigt
// attendOnlineEvent("concert"); // Fejl: Argument af typen '"concert"' kan ikke tildeles parameteren af typen '"webinar"'.

NonNullable

NonNullable konstruerer en type ved at udelukke null og undefined fra T.

Eksempel:


type MaybeString = string | null | undefined;

type DefinitelyString = NonNullable<MaybeString>;

// DefinitelyString er nu string

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

// processString(null); // Fejl: Argument af typen 'null' kan ikke tildeles parameteren af typen 'string'.
// processString(undefined); // Fejl: Argument af typen 'undefined' kan ikke tildeles parameteren af typen 'string'.
processString("hello"); // Gyldigt

ReturnType

ReturnType konstruerer en type bestående af returtypen for funktionen T.

Eksempel:


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

type Greeting = ReturnType<typeof greet>;

// Greeting er nu string

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

console.log(message);

Parameters

Parameters konstruerer en tupletype fra typerne af parametrene for en funktionstype T.

Eksempel:


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

type LogEventParams = Parameters<typeof logEvent>;

// LogEventParams er nu [eventName: string, eventData: object]

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

logEvent(...params);

ConstructorParameters

ConstructorParameters konstruerer en tuple- eller arraytype fra typerne af parametrene for en konstruktørfunktionstype T. Den udleder typerne af de argumenter, der skal sendes til konstruktøren af 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 nu [message: string]

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

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

Required

Required konstruerer en type bestående af alle egenskaber i T indstillet til påkrævet. Det gør alle valgfrie egenskaber påkrævede.

Eksempel:


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

type RequiredUserProfile = Required<UserProfile>;

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

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

// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Fejl: Egenskaben 'age' mangler i typen '{ name: string; }', men den er påkrævet i typen 'Required'.

Avancerede Utility Typer

Template Literal Typer

Template literal typer giver dig mulighed for at konstruere nye strengliteraltyper ved at sammenkæde eksisterende strengliteraltyper, talliteraltyper og mere. Dette muliggør kraftfuld strengbaseret typemanipulation.

Eksempel:


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

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

// RequestURL er nu "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(`Laver anmodning til ${url}`);
}

makeRequest("GET /api/users"); // Gyldigt
// makeRequest("INVALID /api/users"); // Fejl

Conditional Typer

Conditional typer giver dig mulighed for at definere typer, der afhænger af en betingelse udtrykt som et typeforhold. De bruger nøgleordet infer til at udtrække typeinformation.

Eksempel:


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

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

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


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

// Data er nu number

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

processData(await fetchData());

Praktiske Anvendelser og Scenarier fra den Virkelige Verden

Lad os udforske mere komplekse scenarier fra den virkelige verden, hvor utility typer skinner.

1. Formularhåndtering

Når du arbejder med formularer, har du ofte scenarier, hvor du skal repræsentere de indledende formularværdier, de opdaterede formularværdier og de endelige indsendte værdier. Utility typer kan hjælpe dig med at administrere disse forskellige tilstande effektivt.


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

// Indledende formularværdier (valgfrie felter)
type InitialFormValues = Partial<FormData>;

// Opdaterede formularværdier (nogle felter mangler muligvis)
type UpdatedFormValues = Partial<FormData>;

// Påkrævede felter til indsendelse
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;

// Brug disse typer i dine formularkomponenter
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" }; // FEJL: Mangler 'country' 
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; //OK


2. API-datatransformation

Når du bruger data fra en API, skal du muligvis transformere dataene til et andet format til din applikation. Utility typer kan hjælpe dig med at definere strukturen af de transformerede data.


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

// Transformer API-responset til et mere læsbart 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 endda håndhæve typen ved at:

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 af Konfigurationsobjekter

Konfigurationsobjekter er almindelige i mange applikationer. Utility typer kan hjælpe dig med at definere strukturen af konfigurationsobjektet og sikre, at det bruges korrekt.


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

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

// Funktion til at flette brugerindstillinger med standardindstillinger
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
  return { ...defaultSettings, ...userSettings };
}

// Brug de flettede indstillinger i din applikation
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);

Tips til Effektiv Brug af Utility Typer

  • Start simpelt: Begynd med grundlæggende utility typer som Partial og Readonly, før du går videre til mere komplekse.
  • Brug beskrivende navne: Giv dine typealiaser meningsfulde navne for at forbedre læsbarheden.
  • Kombiner utility typer: Du kan kombinere flere utility typer for at opnå komplekse typetransformationer.
  • Udnyt editorstøtte: Udnyt TypeScript's fremragende editorstøtte til at udforske effekterne af utility typer.
  • Forstå de underliggende koncepter: En solid forståelse af TypeScript's typesystem er afgørende for effektiv brug af utility typer.

Konklusion

TypeScript utility typer er kraftfulde værktøjer, der markant kan forbedre kvaliteten og vedligeholdeligheden af din kode. Ved at forstå og anvende disse utility typer effektivt, kan du skrive renere, mere typesikre og mere robuste applikationer, der opfylder kravene i et globalt udviklingslandskab. Denne guide har givet et omfattende overblik over almindelige utility typer og praktiske eksempler. Eksperimenter med dem, og udforsk deres potentiale til at forbedre dine TypeScript-projekter. Husk at prioritere læsbarhed og klarhed, når du bruger utility typer, og stræb altid efter at skrive kode, der er let at forstå og vedligeholde, uanset hvor dine medudviklere er placeret.