Utforska TypeScript Template Literal Types och bygg en körningsvalideringsmotor för robust strängverifiering och typsäkerhet. Lär dig förhindra fel genom att validera strängar mot dina definierade malliteraltyper vid körning.
TypeScript Template Literal Valideringsmotor: Strängverifiering vid körning
TypeScripts malliteraltyper (template literal types) erbjuder kraftfull strängmanipulering och typsäkerhet vid kompileringstillfället. Dessa kontroller är dock begränsade till kompileringstiden. Detta blogginlägg utforskar hur man bygger en valideringsmotor för körning av TypeScript malliteraltyper, vilket möjliggör robust strängverifiering och förhindrar potentiella fel under programkörning.
Introduktion till TypeScript Template Literal Types
Malliteraltyper låter dig definiera specifika strängformer baserat på literala värden, unioner och typinferens. Detta möjliggör exakt typkontroll och automatisk komplettering, vilket är särskilt användbart vid hantering av strukturerad data eller domänspecifika språk.
Tänk till exempel på en typ för att representera valutakoder:
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const validCurrency: FormattedCurrencyString = "USD-100"; // OK
const invalidCurrency: FormattedCurrencyString = "CAD-50"; // Typfel vid kompilering
Detta exempel visar hur TypeScript upprätthåller typen FormattedCurrencyString vid kompileringstillfället. Men om valutakoden kommer från en extern källa (t.ex. användarinmatning, API-svar), behöver du validering vid körning för att säkerställa typsäkerhet.
Behovet av körningsvalidering
Även om TypeScript ger utmärkt typkontroll vid kompilering, kan det inte garantera giltigheten av data som tas emot från externa källor vid körning. Att enbart förlita sig på kompileringstyper kan leda till oväntade fel och sårbarheter.
Tänk på följande scenario:
function processCurrency(currencyString: FormattedCurrencyString) {
// ... lite logik som antar att strängen är korrekt formaterad
}
const userInput = "CAD-50"; // Anta att detta kommer från användarinmatning
// Detta kommer att kompileras, men kommer att orsaka ett körningsfel om logiken inuti
// `processCurrency` förlitar sig på formatet.
processCurrency(userInput as FormattedCurrencyString);
I det här fallet gör vi en type cast av userInput till FormattedCurrencyString, vilket kringgår TypeScripts kompileringstids-kontroller. Om processCurrency förlitar sig på att strängen är korrekt formaterad, kommer det att uppstå ett körningsfel.
Körningsvalidering överbryggar detta gap genom att verifiera att data som tas emot vid körning överensstämmer med de förväntade TypeScript-typerna.
Bygga en valideringsmotor för malliteraler
Vi kan bygga en valideringsmotor för körning med hjälp av reguljära uttryck och TypeScripts typsystem. Motorn kommer att ta en malliteraltyp och en sträng som indata och returnera om strängen matchar typen.
Steg 1: Definiera en typ för körningsvalidering
Först behöver vi en generisk typ som kan representera körningsekvivalenten av en malliteraltyp. Denna typ bör kunna hantera olika sorters malliteraler, inklusive literaler, unioner och typparametrar.
type TemplateLiteralToRegex =
T extends `${infer Start}${infer Middle}${infer End}`
? Start extends string
? Middle extends string
? End extends string
? TemplateLiteralToRegexStart & TemplateLiteralToRegexMiddle & TemplateLiteralToRegex
: never
: never
: never
: TemplateLiteralToRegexStart;
type TemplateLiteralToRegexStart = T extends `${infer Literal}` ? Literal : string;
type TemplateLiteralToRegexMiddle = T extends `${infer Literal}` ? Literal : string;
Denna rekursiva typdefinition bryter ner malliteralen i dess beståndsdelar och omvandlar varje del till ett mönster för reguljära uttryck.
Steg 2: Implementera valideringsfunktionen
Därefter implementerar vi valideringsfunktionen som tar malliteraltypen och strängen som ska valideras som indata. Denna funktion använder det reguljära uttrycket som genereras av TemplateLiteralToRegex för att testa strängen.
function isValid(str: string, templateType: T): boolean {
const regexPattern = `^${convertTemplateLiteralToRegex(templateType)}$`;
const regex = new RegExp(regexPattern);
return regex.test(str);
}
function convertTemplateLiteralToRegex(templateType: T): string {
// Grundläggande konvertering för literala strängar - utöka detta för mer komplexa scenarier
return templateType.replace(/[.*+?^${}()|[\]]/g, '\\$&'); // Escape:a speciella regex-tecken
}
Denna funktion escape:ar speciella tecken för reguljära uttryck och skapar ett reguljärt uttryck från malliteraltypen, och testar sedan strängen mot det reguljära uttrycket.
Steg 3: Använda valideringsmotorn
Nu kan du använda funktionen isValid för att validera strängar mot dina malliteraltyper vid körning.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const userInput1 = "USD-100";
const userInput2 = "CAD-50";
console.log(`'${userInput1}' är giltig: ${isValid(userInput1, "USD-100" )}`); // true
console.log(`'${userInput2}' är giltig: ${isValid(userInput2, "USD-100")}`); // false
console.log(`'${userInput1}' är giltig: ${isValid(userInput1, `USD-${100}`)}`); // true
console.log(`'${userInput2}' är giltig: ${isValid(userInput2, `USD-${100}`)}`); // false
Detta exempel visar hur man använder funktionen isValid för att validera användarinmatning mot typen FormattedCurrencyString. Utdata kommer att visa om indatasträngarna anses vara giltiga eller inte, baserat på den angivna malliteralen.
Avancerade valideringsscenarier
Den grundläggande valideringsmotorn kan utökas för att hantera mer komplexa scenarier, såsom unioner, villkorliga typer och rekursiva typer.
Hantering av unioner
För att hantera unioner kan du modifiera typen TemplateLiteralToRegex för att generera ett reguljärt uttryck som matchar någon av unionsmedlemmarna.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
function isValidCurrencyCode(str: string, templateType: T): boolean {
const currencyCodes: CurrencyCode[] = ["USD", "EUR", "GBP"];
return currencyCodes.includes(str as CurrencyCode);
}
function isValidUnionFormattedCurrencyString(str: string): boolean {
const parts = str.split('-');
if(parts.length !== 2) return false;
const [currencyCode, amount] = parts;
if (!isValidCurrencyCode(currencyCode, currencyCode)) return false;
if (isNaN(Number(amount))) return false;
return true;
}
console.log(`'USD-100' är en giltig formaterad sträng: ${isValidUnionFormattedCurrencyString('USD-100')}`);
console.log(`'CAD-50' är en giltig formaterad sträng: ${isValidUnionFormattedCurrencyString('CAD-50')}`);
Hantering av villkorliga typer
Villkorliga typer kan hanteras genom att utvärdera villkoret vid körning och generera olika reguljära uttryck baserat på resultatet.
type IsString = T extends string ? true : false;
// Detta exempel kräver mer avancerad logik och är inte fullt implementerbart med enkla regex.
// "Type guards" för körning erbjuder en mer robust lösning i detta specifika scenario.
// Koden nedan är illustrativ och skulle behöva anpassas för att hantera komplexa villkorliga typer.
function isString(value: any): value is string {
return typeof value === 'string';
}
function isValidConditionalType(value: any): boolean {
return isString(value);
}
console.log(`'hello' är en sträng: ${isValidConditionalType('hello')}`);
console.log(`123 är en sträng: ${isValidConditionalType(123)}`);
Hantering av rekursiva typer
Rekursiva typer kan hanteras genom att definiera en rekursiv funktion som genererar mönstret för det reguljära uttrycket. Var dock försiktig så att du undviker oändlig rekursion och stack overflow-fel. För djup rekursion är iterativa tillvägagångssätt med lämpliga gränser avgörande.
Alternativ till reguljära uttryck
Även om reguljära uttryck är ett kraftfullt verktyg för strängvalidering, kan de vara komplexa och svåra att underhålla. Andra metoder för körningsvalidering inkluderar:
- Anpassade valideringsfunktioner: Skriv anpassade funktioner för att validera specifika typer baserat på din applikations krav.
- Type Guards: Använd "type guards" för att avgränsa typen av en variabel vid körning.
- Valideringsbibliotek: Utnyttja befintliga valideringsbibliotek som Zod eller Yup för att förenkla valideringsprocessen.
Zod, till exempel, tillhandahåller en schemabaserad deklaration som översätts till en valideringskörning:
import { z } from 'zod';
const CurrencyCodeSchema = z.enum(['USD', 'EUR', 'GBP']);
const FormattedCurrencyStringSchema = z.string().regex(new RegExp(`^${CurrencyCodeSchema.enum.USD}|${CurrencyCodeSchema.enum.EUR}|${CurrencyCodeSchema.enum.GBP}-[0-9]+$`));
try {
const validCurrency = FormattedCurrencyStringSchema.parse("USD-100");
console.log("Giltig valuta:", validCurrency);
} catch (error) {
console.error("Ogiltig valuta:", error);
}
try {
const invalidCurrency = FormattedCurrencyStringSchema.parse("CAD-50");
console.log("Giltig valuta:", invalidCurrency); //Detta kommer inte att köras om "parse" misslyckas.
} catch (error) {
console.error("Ogiltig valuta:", error);
}
Bästa praxis för körningsvalidering
När du implementerar körningsvalidering, tänk på följande bästa praxis:
- Validera vid gränssnittet: Validera data så snart det kommer in i ditt system (t.ex. användarinmatning, API-svar).
- Ge tydliga felmeddelanden: Generera informativa felmeddelanden för att hjälpa användare att förstå varför deras inmatning är ogiltig.
- Använd en konsekvent valideringsstrategi: Anta en konsekvent valideringsstrategi i hela din applikation för att säkerställa dataintegritet.
- Testa din valideringslogik: Testa din valideringslogik noggrant för att säkerställa att den korrekt identifierar giltig och ogiltig data.
- Balansera prestanda och säkerhet: Optimera din valideringslogik för prestanda samtidigt som du säkerställer att den effektivt förhindrar säkerhetssårbarheter. Undvik alltför komplexa regex som leder till denial of service.
Hänsyn till internationalisering
När du hanterar strängvalidering i ett globalt sammanhang måste du ta hänsyn till internationalisering (i18n) och lokalisering (l10n). Olika lokaler kan ha olika regler för formatering av strängar, såsom datum, siffror och valutavärden.
Till exempel kan valutasymbolen för euron (€) visas före eller efter beloppet, beroende på lokal. Likaså kan decimalavgränsaren vara en punkt (.) eller ett kommatecken (,).
För att hantera dessa variationer kan du använda internationaliseringsbibliotek som Intl, som tillhandahåller API:er för formatering och tolkning av lokal-känslig data. Du kan till exempel anpassa det föregående exemplet för att hantera olika valutaformat:
function isValidCurrencyString(currencyString: string, locale: string): boolean {
try {
const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyString.substring(0,3) }); //Mycket grundläggande exempel
//Försök att tolka valutan med formateraren. Detta exempel är avsiktligt mycket enkelt.
return true;
} catch (error) {
return false;
}
}
console.log(`USD-100 är giltig för en-US: ${isValidCurrencyString('USD-100', 'en-US')}`);
console.log(`EUR-100 är giltig för fr-FR: ${isValidCurrencyString('EUR-100', 'fr-FR')}`);
Detta kodavsnitt ger ett grundläggande exempel. Korrekt internationalisering kräver mer grundlig hantering, eventuellt med hjälp av externa bibliotek eller API:er som är specifikt utformade för valutaformatering och validering över olika lokaler.
Slutsats
Körningsvalidering är en väsentlig del av att bygga robusta och pålitliga TypeScript-applikationer. Genom att kombinera TypeScripts malliteraltyper med reguljära uttryck eller alternativa valideringsmetoder kan du skapa en kraftfull motor för att verifiera giltigheten av strängar vid körning.
Detta tillvägagångssätt förbättrar typsäkerheten, förhindrar oväntade fel och förbättrar den övergripande kvaliteten på din kod. När du bygger mer komplexa applikationer, överväg att införliva körningsvalidering för att säkerställa att dina data överensstämmer med de förväntade typerna och formaten.
Vidare utforskning
- Utforska avancerade tekniker för reguljära uttryck för mer komplexa valideringsscenarier.
- Undersök valideringsbibliotek som Zod och Yup för schemabaserad validering.
- Överväg att använda kodgenereringstekniker för att automatiskt generera valideringsfunktioner från TypeScript-typer.
- Studera internationaliseringsbibliotek och API:er för att hantera lokal-känslig data.