Lås upp kraften i TypeScript utility types för renare, mer underhållsbar och typsäker kod. Utforska praktiska tillämpningar med verkliga exempel för globala utvecklare.
Bemästra TypeScript Utility Types: En Praktisk Guide för Globala Utvecklare
TypeScript erbjuder en kraftfull uppsättning inbyggda utility types som avsevärt kan förbättra din kods typsäkerhet, läsbarhet och underhållbarhet. Dessa utility types är i grunden fördefinierade typtransformationer som du kan tillämpa på befintliga typer, vilket sparar dig från att skriva repetitiv och felbenägen kod. Denna guide kommer att utforska olika utility types med praktiska exempel som är relevanta för utvecklare över hela världen.
Varför Använda Utility Types?
Utility types hanterar vanliga scenarier för typmanipulation. Genom att utnyttja dem kan du:
- Minska boilerplate-kod: Undvik att skriva repetitiva typdefinitioner.
- Förbättra typsäkerheten: Säkerställ att din kod följer typbegränsningar.
- Förbättra kodens läsbarhet: Gör dina typdefinitioner mer koncisa och lättare att förstå.
- Öka underhållbarheten: Förenkla modifieringar och minska risken för att introducera fel.
Kärn-Utility Types
Partial<T>
Partial<T>
konstruerar en typ där alla egenskaper för T
sätts till valfria. Detta är särskilt användbart när du vill skapa en typ för partiella uppdateringar eller konfigurationsobjekt.
Exempel:
Föreställ dig att du bygger en e-handelsplattform med kunder från olika regioner. Du har en Customer
-typ:
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 uppdaterar en kunds information kanske du inte vill kräva alla fält. Partial<Customer>
låter dig definiera en typ där alla egenskaper för Customer
är valfria:
type PartialCustomer = Partial<Customer>;
function updateCustomer(id: string, updates: PartialCustomer): void {
// ... implementation to update the customer with the given ID
}
updateCustomer("123", { firstName: "John", lastName: "Doe" }); // Valid
updateCustomer("456", { address: { city: "London" } }); // Valid
Readonly<T>
Readonly<T>
konstruerar en typ där alla egenskaper för T
sätts till readonly
, vilket förhindrar modifiering efter initialisering. Detta är värdefullt för att säkerställa oföränderlighet.
Exempel:
Tänk dig ett konfigurationsobjekt för din globala applikation:
interface AppConfig {
apiUrl: string;
theme: string;
supportedLanguages: string[];
version: string; // Added version
}
const config: AppConfig = {
apiUrl: "https://api.example.com",
theme: "dark",
supportedLanguages: ["en", "fr", "de", "es", "zh"],
version: "1.0.0"
};
För att förhindra oavsiktlig modifiering av konfigurationen efter initialisering kan du använda 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"; // Error: Cannot assign to 'apiUrl' because it is a read-only property.
Pick<T, K>
Pick<T, K>
konstruerar en typ genom att välja uppsättningen egenskaper K
från T
, där K
är en union av stränglitteraltyper som representerar de egenskapsnamn du vill inkludera.
Exempel:
Anta att du har ett Event
-gränssnitt med olika egenskaper:
interface Event {
id: string;
title: string;
description: string;
location: string;
startTime: Date;
endTime: Date;
organizer: string;
attendees: string[];
}
Om du bara behöver title
, location
och startTime
för en specifik visningskomponent kan du använda 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>
konstruerar en typ genom att exkludera uppsättningen egenskaper K
från T
, där K
är en union av stränglitteraltyper som representerar de egenskapsnamn du vill exkludera. Detta är motsatsen till Pick
.
Exempel:
Med samma Event
-gränssnitt, om du vill skapa en typ för nya händelser, kanske du vill exkludera id
-egenskapen, som typiskt genereras av backend:
type NewEvent = Omit<Event, "id">;
function createEvent(event: NewEvent): void {
// ... implementation to create a new event
}
Record<K, T>
Record<K, T>
konstruerar en objekttyp vars egenskapsnycklar är K
och vars egenskapsvärden är T
. K
kan vara en union av stränglitteraltyper, nummerlitteraltyper eller en symbol. Detta är perfekt för att skapa ordlistor eller mappningar.
Exempel:
Föreställ dig att du behöver lagra översättningar för din applikations användargränssnitt. Du kan använda Record
för att definiera en typ för dina översättningar:
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<T, U>
Exclude<T, U>
konstruerar en typ genom att exkludera från T
alla unionmedlemmar som kan tilldelas U
. Det är användbart för att filtrera bort specifika typer från en union.
Exempel:
Du kanske har en typ som representerar olika händelsetyper:
type EventType = "concert" | "conference" | "workshop" | "webinar";
Om du vill skapa en typ som exkluderar "webinar"-händelser kan du använda Exclude
:
type PhysicalEvent = Exclude<EventType, "webinar">;
// PhysicalEvent is now "concert" | "conference" | "workshop"
function attendPhysicalEvent(event: PhysicalEvent): void {
console.log(`Attending a ${event}`);
}
// attendPhysicalEvent("webinar"); // Error: Argument of type '"webinar"' is not assignable to parameter of type '"concert" | "conference" | "workshop"'.
attendPhysicalEvent("concert"); // Valid
Extract<T, U>
Extract<T, U>
konstruerar en typ genom att extrahera från T
alla unionmedlemmar som kan tilldelas U
. Detta är motsatsen till Exclude
.
Exempel:
Med samma EventType
kan du extrahera webbinarhändelsetypen:
type OnlineEvent = Extract<EventType, "webinar">;
// OnlineEvent is now "webinar"
function attendOnlineEvent(event: OnlineEvent): void {
console.log(`Attending a ${event} online`);
}
attendOnlineEvent("webinar"); // Valid
// attendOnlineEvent("concert"); // Error: Argument of type '"concert"' is not assignable to parameter of type '"webinar"'.
NonNullable<T>
NonNullable<T>
konstruerar en typ genom att exkludera null
och undefined
från T
.
Exempel:
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;
// DefinitelyString is now string
function processString(str: DefinitelyString): void {
console.log(str.toUpperCase());
}
// processString(null); // Error: Argument of type 'null' is not assignable to parameter of type 'string'.
// processString(undefined); // Error: Argument of type 'undefined' is not assignable to parameter of type 'string'.
processString("hello"); // Valid
ReturnType<T>
ReturnType<T>
konstruerar en typ som består av returtypen för funktion T
.
Exempel:
function greet(name: string): string {
return `Hello, ${name}!`;
}
type Greeting = ReturnType<typeof greet>;
// Greeting is now string
const message: Greeting = greet("World");
console.log(message);
Parameters<T>
Parameters<T>
konstruerar en tuppeltyp från typerna av parametrarna för en funktionstyp T
.
Exempel:
function logEvent(eventName: string, eventData: object): void {
console.log(`Event: ${eventName}`, eventData);
}
type LogEventParams = Parameters<typeof logEvent>;
// LogEventParams is now [eventName: string, eventData: object]
const params: LogEventParams = ["user_login", { userId: "123", timestamp: Date.now() }];
logEvent(...params);
ConstructorParameters<T>
ConstructorParameters<T>
konstruerar en tuppel- eller arraytyp från typerna av parametrarna för en konstruktorfunktionstyp T
. Den infererar typerna av de argument som behöver skickas till konstruktorn för en klass.
Exempel:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
type GreeterParams = ConstructorParameters<typeof Greeter>;
// GreeterParams is now [message: string]
const paramsGreeter: GreeterParams = ["World"];
const greeterInstance = new Greeter(...paramsGreeter);
console.log(greeterInstance.greet()); // Outputs: Hello, World
Required<T>
Required<T>
konstruerar en typ som består av alla egenskaper för T
som är satta till obligatoriska. Den gör alla valfria egenskaper obligatoriska.
Exempel:
interface UserProfile {
name: string;
age?: number;
email?: string;
}
type RequiredUserProfile = Required<UserProfile>;
// RequiredUserProfile is now { name: string; age: number; email: string; }
const completeProfile: RequiredUserProfile = {
name: "Alice",
age: 30,
email: "alice@example.com"
};
// const incompleteProfile: RequiredUserProfile = { name: "Bob" }; // Error: Property 'age' is missing in type '{ name: string; }' but required in type 'Required'.
Avancerade Utility Types
Mall-Literal-Typer (Template Literal Types)
Mall-literal-typer (Template literal types) låter dig konstruera nya stränglitteraltyper genom att sammanfoga befintliga stränglitteraltyper, nummerlitteraltyper och mer. Detta möjliggör kraftfull strängbaserad typmanipulation.
Exempel:
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/users` | `/api/products`;
type RequestURL = `${HTTPMethod} ${APIEndpoint}`;
// RequestURL is now "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"); // Valid
// makeRequest("INVALID /api/users"); // Error
Villkorliga Typer (Conditional Types)
Villkorliga typer (Conditional types) låter dig definiera typer som beror på ett villkor uttryckt som en typrelation. De använder nyckelordet infer
för att extrahera typinformation.
Exempel:
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
// If T is a Promise, then the type is U; otherwise, the type is T.
async function fetchData(): Promise<number> {
return 42;
}
type Data = UnwrapPromise<ReturnType<typeof fetchData>>;
// Data is now number
function processData(data: Data): void {
console.log(data * 2);
}
processData(await fetchData());
Praktiska Tillämpningar och Verkliga Scenarier
Låt oss utforska mer komplexa verkliga scenarier där utility types briljerar.
1. Formulärhantering
När du hanterar formulär har du ofta scenarier där du behöver representera de initiala formulärvärdena, de uppdaterade formulärvärdena och de slutgiltigt inskickade värdena. Utility types kan hjälpa dig att hantera dessa olika tillstånd effektivt.
interface FormData {
firstName: string;
lastName: string;
email: string;
country: string; // Required
city?: string; // Optional
postalCode?: string;
newsletterSubscription?: boolean;
}
// Initiala formulärvärden (valfria fält)
type InitialFormValues = Partial<FormData>;
// Uppdaterade formulärvärden (vissa fält kan saknas)
type UpdatedFormValues = Partial<FormData>;
// Obligatoriska fält för inskickning
type RequiredForSubmission = Required<Pick<FormData, 'firstName' | 'lastName' | 'email' | 'country'>>;
// Använd dessa typer i dina formulärkomponenter
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" }; // ERROR: Missing 'country'
const submissionData: RequiredForSubmission = { firstName: "test", lastName: "test", email: "test", country: "USA" }; //OK
2. API-datatransformering
När du konsumerar data från ett API kan du behöva transformera datan till ett annat format för din applikation. Utility types kan hjälpa dig att definiera strukturen för den transformerade datan.
interface APIResponse {
user_id: string;
first_name: string;
last_name: string;
email_address: string;
profile_picture_url: string;
is_active: boolean;
}
// Transformera API-svaret till ett mer 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 till och med tvinga fram typen genom att:
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. Hantering av konfigurationsobjekt
Konfigurationsobjekt är vanliga i många applikationer. Utility types kan hjälpa dig att definiera strukturen för konfigurationsobjektet och säkerställa att det används korrekt.
interface AppSettings {
theme: "light" | "dark";
language: string;
notificationsEnabled: boolean;
apiUrl?: string; // Valfri API-URL för olika miljöer
timeout?: number; //Valfri
}
// Standardinställningar
const defaultSettings: AppSettings = {
theme: "light",
language: "en",
notificationsEnabled: true
};
// Funktion för att slå samman användarinställningar med standardinställningar
function mergeSettings(userSettings: Partial<AppSettings>): AppSettings {
return { ...defaultSettings, ...userSettings };
}
// Använd de sammanfogade inställningarna i din applikation
const mergedSettings = mergeSettings({ theme: "dark", apiUrl: "https://customapi.example.com" });
console.log(mergedSettings);
Tips för Effektiv Användning av Utility Types
- Börja enkelt: Börja med grundläggande utility types som
Partial
ochReadonly
innan du går vidare till mer komplexa. - Använd beskrivande namn: Ge dina typalias meningsfulla namn för att förbättra läsbarheten.
- Kombinera utility types: Du kan kombinera flera utility types för att uppnå komplexa typtransformationer.
- Utnyttja redigeringsstöd: Dra nytta av TypeScript:s utmärkta redigeringsstöd för att utforska effekterna av utility types.
- Förstå de underliggande koncepten: En solid förståelse för TypeScript:s typsystem är avgörande för effektiv användning av utility types.
Slutsats
TypeScript utility types är kraftfulla verktyg som avsevärt kan förbättra kvaliteten och underhållbarheten i din kod. Genom att förstå och tillämpa dessa utility types effektivt kan du skriva renare, mer typsäkra och robusta applikationer som möter kraven från ett globalt utvecklingslandskap. Denna guide har gett en omfattande översikt över vanliga utility types och praktiska exempel. Experimentera med dem och utforska deras potential att förbättra dina TypeScript-projekt. Kom ihåg att prioritera läsbarhet och tydlighet när du använder utility types, och sträva alltid efter att skriva kod som är lätt att förstå och underhålla, oavsett var dina utvecklarkollegor befinner sig.