Udforsk avancerede TypeScript-funktioner som template literal-typer og betingede typer for at skrive mere ekspressiv og vedligeholdelig kode. Mestre typemanipulation til komplekse scenarier.
TypeScript Avancerede Typer: Mestring af Template Literal og Betingede Typer
TypeScript's styrke ligger i dets kraftfulde typesystem. Selvom basale typer som string, number og boolean er tilstrækkelige i mange scenarier, åbner avancerede funktioner som template literal-typer og betingede typer op for et nyt niveau af ekspressivitet og typesikkerhed. Denne guide giver et omfattende overblik over disse avancerede typer, udforsker deres muligheder og demonstrerer praktiske anvendelser.
Forståelse af Template Literal Typer
Template literal-typer bygger på JavaScripts template literals, hvilket giver dig mulighed for at definere typer baseret på strenginterpolation. Dette muliggør oprettelsen af typer, der repræsenterer specifikke strengmønstre, hvilket gør din kode mere robust og forudsigelig.
Grundlæggende Syntaks og Brug
Template literal-typer bruger backticks (`) til at omgive typedefinitionen, svarende til JavaScript template literals. Inden for backticks kan du interpolere andre typer ved hjælp af ${} syntaksen. Det er her magien sker – du opretter i det væsentlige en type, der er en streng, konstrueret på kompileringstidspunktet baseret på typerne inde i interpolationen.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Example Usage
const getEndpoint: APIEndpoint = "/api/users"; // Valid
const postEndpoint: APIEndpoint = "/api/products/123"; // Valid
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript will not show an error here as `string` can be anything
I dette eksempel er APIEndpoint en type, der repræsenterer enhver streng, der starter med /api/. Selvom dette grundlæggende eksempel er nyttigt, kommer den sande styrke ved template literal-typer til udtryk, når de kombineres med mere specifikke typebegrænsninger.
Kombinering med Union Typer
Template literal-typer skinner virkelig, når de bruges med union typer. Dette giver dig mulighed for at oprette typer, der repræsenterer et specifikt sæt strengkombinationer.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Valid API Endpoints
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Invalid API Endpoints (will result in TypeScript errors)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Error: "/users/PATCH" is not assignable to type "/users/GET" | "/users/POST" | "/users/PUT" | "/users/DELETE" | "/products/GET" | "/products/POST" | ... 3 more ... | "/orders/DELETE".
Nu er APIEndpoint en mere restriktiv type, der kun tillader specifikke kombinationer af API-stier og HTTP-metoder. TypeScript vil markere ethvert forsøg på at bruge ugyldige kombinationer, hvilket forbedrer typesikkerheden.
Strengmanipulation med Template Literal Typer
TypeScript tilbyder indbyggede strengmanipulationstyper, der fungerer problemfrit med template literal-typer. Disse typer giver dig mulighed for at transformere strenge på kompileringstidspunktet.
- Uppercase: Konverterer en streng til store bogstaver.
- Lowercase: Konverterer en streng til små bogstaver.
- Capitalize: Skriver det første bogstav i en streng med stort.
- Uncapitalize: Skriver det første bogstav i en streng med småt.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
Disse strengmanipulationstyper er især nyttige til automatisk at generere typer baseret på navngivningskonventioner. For eksempel kan du udlede handlingstyper fra begivenhedsnavne eller omvendt.
Praktiske Anvendelser af Template Literal Typer
- API Endpoint Definition: Som demonstreret ovenfor, definering af API-endpoints med præcise typebegrænsninger.
- Event Handling: Oprettelse af typer til begivenhedsnavne med specifikke præfikser og suffikser.
- CSS Class Generation: Generering af CSS-klassenavne baseret på komponentnavne og tilstande.
- Database Query Building: Sikring af typesikkerhed ved konstruktion af databaseforespørgsler.
Internationalt Eksempel: Valutaformatering
Forestil dig at bygge en finansiel applikation, der understøtter flere valutaer. Du kan bruge template literal-typer til at håndhæve korrekt valutaformatering.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // Valid
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Valid
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Error: Type 'string' is not assignable to type '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Type: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Type: "100 EUR"
Dette eksempel sikrer, at valutaværdier altid formateres med den korrekte valutakode, hvilket forhindrer potentielle fejl.
Dybere Ind i Betingede Typer
Betingede typer introducerer forgreningslogik i TypeScript's typesystem, hvilket giver dig mulighed for at definere typer, der afhænger af andre typer. Denne funktion er utrolig kraftfuld til at skabe meget fleksible og genanvendelige typedefinitioner.
Grundlæggende Syntaks og Brug
Betingede typer bruger infer nøgleordet og den ternære operator (condition ? trueType : falseType) til at definere typebetingelser.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // type StringCheck = true
type NumberCheck = IsString; // type NumberCheck = false
I dette eksempel er IsString en betinget type, der kontrollerer, om T kan tildeles til string. Hvis det er tilfældet, opløses typen til true; ellers opløses den til false.
infer Nøgleordet
infer nøgleordet giver dig mulighed for at udtrække en type fra en type. Dette er især nyttigt, når du arbejder med komplekse typer som funktionstyper eller arraytyper.
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // type AddReturnType = number
I dette eksempel udtrækker ReturnType returtypen for en funktionstype T. infer R delen af den betingede type udleder returtypen og tildeler den til typevariablen R. Hvis T ikke er en funktionstype, opløses typen til any.
Distributive Betingede Typer
Betingede typer bliver distributive, når den kontrollerede type er en nøgen typeparameter. Det betyder, at den betingede type anvendes på hvert medlem af unionstypen separat.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // type NumberOrStringArray = string[] | number[]
I dette eksempel konverterer ToArray en type T til en arraytype. Fordi T er en nøgen typeparameter (ikke omviklet i en anden type), anvendes den betingede type på number og string separat, hvilket resulterer i en union af number[] og string[].
Praktiske Anvendelser af Betingede Typer
- Udtrækning af Returtyper: Som demonstreret ovenfor, udtrækning af returtypen for en funktion.
- Filtrering af Typer fra en Union: Oprettelse af en type, der kun indeholder specifikke typer fra en union.
- Definering af Overloaded Funktionstyper: Oprettelse af forskellige funktionstyper baseret på inputtyper.
- Oprettelse af Type Guards: Definering af funktioner, der indsnævrer typen af en variabel.
Internationalt Eksempel: Håndtering af Forskellige Datoformater
Forskellige regioner i verden bruger forskellige datoformater. Du kan bruge betingede typer til at håndtere disse variationer.
type DateFormat = "YYYY-MM-DD" | "MM/DD/YYYY" | "DD.MM.YYYY";
type ParseDate = T extends "YYYY-MM-DD"
? { year: number; month: number; day: number; format: "YYYY-MM-DD" }
: T extends "MM/DD/YYYY"
? { month: number; day: number; year: number; format: "MM/DD/YYYY" }
: T extends "DD.MM.YYYY"
? { day: number; month: number; year: number; format: "DD.MM.YYYY" }
: never;
function parseDate(dateString: string, format: T): ParseDate {
// (Implementation would handle different date formats)
if (format === "YYYY-MM-DD") {
const [year, month, day] = dateString.split("-").map(Number);
return { year, month, day, format } as ParseDate;
} else if (format === "MM/DD/YYYY") {
const [month, day, year] = dateString.split("/").map(Number);
return { month, day, year, format } as ParseDate;
} else if (format === "DD.MM.YYYY") {
const [day, month, year] = dateString.split(".").map(Number);
return { day, month, year, format } as ParseDate;
} else {
throw new Error("Invalid date format");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Type: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Type: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Type: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Access the year knowing it will be there
Dette eksempel bruger betingede typer til at definere forskellige datoparsningsfunktioner baseret på det specificerede datoformat. Typen ParseDate sikrer, at det returnerede objekt har de korrekte egenskaber baseret på formatet.
Kombinering af Template Literal og Betingede Typer
Den virkelige styrke kommer, når du kombinerer template literal-typer og betingede typer. Dette giver mulighed for utrolig kraftfuld typemanipulation.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // Simplified for demonstration
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//Example function that takes a type
function processEvent(event: T): ExtractEventPayload {
//In a real implementation, we would actually dispatch the event.
console.log(`Processing event ${event}`);
//In a real implementation, the payload would be based on event type.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Note that the return types are very specific:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//If you use other strings, you get never:
// const someOtherEvent = processEvent("someOtherEvent"); // Type is `never`
Bedste Praksis og Overvejelser
- Hold det Simpelt: Selvom de er kraftfulde, kan disse avancerede typer hurtigt blive komplekse. Stræb efter klarhed og vedligeholdelighed.
- Test Grundigt: Sørg for, at dine typedefinitioner opfører sig som forventet ved at skrive omfattende enhedstests.
- Dokumenter Din Kode: Dokumenter tydeligt formålet og opførslen af dine avancerede typer for at forbedre kodens læsbarhed.
- Overvej Ydeevne: Overdreven brug af avancerede typer kan påvirke kompileringstiden. Profiler din kode og optimer, hvor det er nødvendigt.
Konklusion
Template literal-typer og betingede typer er kraftfulde værktøjer i TypeScript's arsenal. Ved at mestre disse avancerede typer kan du skrive mere ekspressiv, vedligeholdelig og typesikker kode. Disse funktioner giver dig mulighed for at fange komplekse forhold mellem typer, håndhæve strengere begrænsninger og oprette meget genanvendelige typedefinitioner. Omfavn disse teknikker for at løfte dine TypeScript-færdigheder og opbygge robuste og skalerbare applikationer til et globalt publikum.