Prozkoumejte pokročilé funkce TypeScriptu, jako jsou šablonové literálové a podmíněné typy, pro expresivnější a udržovatelnější kód.
TypeScript Pokročilé typy: Zvládnutí šablonových literálových a podmíněných typů
Síla TypeScriptu spočívá v jeho mocném typovém systému. Zatímco základní typy jako string, number a boolean jsou pro mnoho scénářů dostačující, pokročilé funkce, jako jsou šablonové literálové a podmíněné typy, odemykají novou úroveň expresivity a typové bezpečnosti. Tato příručka poskytuje komplexní přehled těchto pokročilých typů, zkoumá jejich možnosti a demonstruje praktické aplikace.
Porozumění šablonovým literálovým typům
Šablonové literálové typy vycházejí z JavaScriptových šablonových literálů a umožňují definovat typy na základě interpolace řetězců. To umožňuje vytvářet typy, které reprezentují specifické vzory řetězců, čímž je váš kód robustnější a předvídatelnější.
Základní syntaxe a použití
Šablonové literálové typy používají zpětné apostrofy (`) k uzavření definice typu, podobně jako JavaScriptové šablonové literály. V rámci zpětných apostrofů můžete interpolovat jiné typy pomocí syntaxe ${}. Zde se děje kouzlo – v podstatě vytváříte typ, který je řetězec, konstruovaný v době kompilace na základě typů uvnitř interpolace.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Příklad použití
const getEndpoint: APIEndpoint = "/api/users"; // Platné
const postEndpoint: APIEndpoint = "/api/products/123"; // Platné
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript zde neukáže chybu, protože `string` může být cokoliv
V tomto příkladu je APIEndpoint typ, který reprezentuje jakýkoli řetězec začínající na /api/. Zatímco tento základní příklad je užitečný, skutečná síla šablonových literálových typů se projeví při kombinaci s konkrétnějšími typovými omezeními.
Kombinace s typovými sjednoceními
Šablonové literálové typy skutečně září, když jsou použity s typovými sjednoceními. To vám umožní vytvářet typy, které reprezentují specifickou sadu kombinací řetězců.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Platné API koncové body
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Neplatné API koncové body (budou mít za následek chyby TypeScriptu)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Chyba: "/users/PATCH" není přiřaditelné k typu "/users/GET" | "/users/POST" | "/users/PUT" | "/users/DELETE" | "/products/GET" | "/products/POST" | ... 3 more ... | "/orders/DELETE".
Nyní je APIEndpoint restriktivnější typ, který povoluje pouze specifické kombinace cest API a metod HTTP. TypeScript označí jakékoli pokusy o použití neplatných kombinací, čímž zvýší typovou bezpečnost.
Manipulace s řetězci pomocí šablonových literálových typů
TypeScript poskytuje vnitřní typy pro manipulaci s řetězci, které bezproblémově fungují se šablonovými literálovými typy. Tyto typy umožňují transformovat řetězce v době kompilace.
- Uppercase: Převede řetězec na velká písmena.
- Lowercase: Převede řetězec na malá písmena.
- Capitalize: Kapitalizuje první písmeno řetězce.
- Uncapitalize: Převede první písmeno řetězce na malé.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
Tyto typy pro manipulaci s řetězci jsou obzvláště užitečné pro automatické generování typů na základě konvencí pojmenování. Například byste mohli odvodit typy akcí z názvů událostí nebo naopak.
Praktické aplikace šablonových literálových typů
- Definice API koncových bodů: Jak bylo ukázáno výše, definování API koncových bodů s přesnými typovými omezeními.
- Zpracování událostí: Vytváření typů pro názvy událostí se specifickými předponami a příponami.
- Generování CSS tříd: Generování názvů CSS tříd na základě názvů komponent a stavů.
- Sestavování dotazů do databáze: Zajištění typové bezpečnosti při sestavování databázových dotazů.
Mezinárodní příklad: Formátování měny
Představte si, že vytváříte finanční aplikaci, která podporuje více měn. Můžete použít šablonové literálové typy k vynucení správného formátování měny.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // Platné
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Platné
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Chyba: Typ 'string' není přiřaditelný k typu '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Typ: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Typ: "100 EUR"
Tento příklad zajišťuje, že měnové hodnoty jsou vždy formátovány se správným kódem měny, čímž se předchází potenciálním chybám.
Ponoření do podmíněných typů
Podmíněné typy zavádějí rozvětvující logiku do typového systému TypeScriptu, což vám umožňuje definovat typy, které závisí na jiných typech. Tato funkce je neuvěřitelně výkonná pro vytváření vysoce flexibilních a znovupoužitelných typových definic.
Základní syntaxe a použití
Podmíněné typy používají klíčové slovo infer a ternární operátor (condition ? trueType : falseType) k definování typových podmínek.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // type StringCheck = true
type NumberCheck = IsString; // type NumberCheck = false
V tomto příkladu je IsString podmíněný typ, který kontroluje, zda je T přiřaditelné k string. Pokud ano, typ se vyřeší na true; jinak se vyřeší na false.
Klíčové slovo infer
Klíčové slovo infer umožňuje extrahovat typ z typu. To je zvláště užitečné při práci se složitými typy, jako jsou typy funkcí nebo typy polí.
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
V tomto příkladu ReturnType extrahuje návratový typ funkce T. Část infer R podmíněného typu odvozuje návratový typ a přiřazuje jej typové proměnné R. Pokud T není typem funkce, typ se vyřeší na any.
Distributivní podmíněné typy
Podmíněné typy se stávají distributivními, když je kontrolovaný typ nahým typovým parametrem. To znamená, že podmíněný typ je aplikován na každý člen typového sjednocení zvlášť.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // type NumberOrStringArray = string[] | number[]
V tomto příkladu ToArray převádí typ T na typ pole. Protože T je nahý typový parametr (není zabalen v jiném typu), podmíněný typ je aplikován na number a string zvlášť, což má za následek sjednocení number[] a string[].
Praktické aplikace podmíněných typů
- Extrahování návratových typů: Jak bylo ukázáno výše, extrahování návratového typu funkce.
- Filtrování typů ze sjednocení: Vytvoření typu, který obsahuje pouze specifické typy ze sjednocení.
- Definování typů přetížených funkcí: Vytváření různých typů funkcí na základě vstupních typů.
- Vytváření typových strážců: Definování funkcí, které zužují typ proměnné.
Mezinárodní příklad: Zpracování různých formátů data
Různé oblasti světa používají různé formáty data. K jejich zpracování můžete použít podmíněné typy.
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 {
// (Implementace by zpracovávala různé formáty data)
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("Neplatný formát data");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Typ: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Typ: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Typ: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Přístup k roku s vědomím, že tam bude
Tento příklad používá podmíněné typy k definování různých funkcí pro parsování dat na základě zadaného formátu data. Typ ParseDate zajišťuje, že vrácený objekt má správné vlastnosti na základě formátu.
Kombinování šablonových literálových a podmíněných typů
Skutečná síla přichází, když zkombinujete šablonové literálové typy a podmíněné typy. To umožňuje neuvěřitelně silné manipulace s typy.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // Zjednodušeno pro demonstraci
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//Příklad funkce, která přijímá typ
function processEvent(event: T): ExtractEventPayload {
//V reálné implementaci bychom skutečně dispatchovali událost.
console.log(`Zpracovávám událost ${event}`);
//V reálné implementaci by se payload zakládal na typu události.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Poznámka, že návratové typy jsou velmi specifické:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//Pokud použijete jiné řetězce, dostanete never:
// const someOtherEvent = processEvent("someOtherEvent"); // Typ je `never`
Osvědčené postupy a doporučení
- Udržujte to jednoduché: I když jsou tyto pokročilé typy mocné, mohou se rychle stát složitými. Snažte se o jasnost a udržovatelnost.
- Důkladně testujte: Zajistěte, aby vaše typové definice fungovaly podle očekávání psaním komplexních jednotkových testů.
- Dokumentujte svůj kód: Jasně zdokumentujte účel a chování svých pokročilých typů pro zlepšení čitelnosti kódu.
- Zvažte výkon: Nadměrné používání pokročilých typů může ovlivnit dobu kompilace. Profilujte svůj kód a optimalizujte tam, kde je to nutné.
Závěr
Šablonové literálové typy a podmíněné typy jsou mocné nástroje v arzenálu TypeScriptu. Zvládnutím těchto pokročilých typů můžete psát expresivnější, udržovatelnější a typově bezpečnější kód. Tyto funkce vám umožňují zachytit složité vztahy mezi typy, vynucovat přísnější omezení a vytvářet vysoce znovupoužitelné typové definice. Přijměte tyto techniky, abyste pozvedli své dovednosti v TypeScriptu a vytvořili robustní a škálovatelné aplikace pro globální publikum.