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:
- Reducere boilerplate-kode: Undgå at skrive gentagne typedefinitioner.
- Forbedre typesikkerheden: Sørg for, at din kode overholder typebegrænsninger.
- Forbedre kodens læsbarhed: Gør dine typedefinitioner mere præcise og nemmere at forstå.
- Øge vedligeholdeligheden: Forenkle ændringer og reducer risikoen for at introducere fejl.
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
ogReadonly
, 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.