Utforsk TypeScript Template Literal-typer og bygg en valideringsmotor for robust strengverifisering og typesikkerhet ved kjøretid. Lær å forhindre feil ved å validere strenger mot dine definerte template literal-typer.
Valideringsmotor for TypeScript Template Literal: Kjøretidsverifisering av strenger
TypeScript sine template literal-typer tilbyr kraftig strengmanipulering og typesikkerhet ved kompileringstid. Disse sjekkene er imidlertid begrenset til kompileringstid. Dette blogginnlegget utforsker hvordan man bygger en valideringsmotor for TypeScript template literal-typer ved kjøretid, noe som muliggjør robust strengverifisering og forhindrer potensielle feil under programkjøring.
Introduksjon til TypeScript Template Literal-typer
Template literal-typer lar deg definere spesifikke strengformer basert på bokstavelige verdier, unioner og typeinferens. Dette muliggjør presis typesjekking og autofullføring, noe som er spesielt nyttig når man jobber med strukturerte data eller domenespesifikke språk.
For eksempel, vurder en type for å representere valutakoder:
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const validCurrency: FormattedCurrencyString = "USD-100"; // OK
const invalidCurrency: FormattedCurrencyString = "CAD-50"; // Typefeil ved kompileringstid
Dette eksempelet demonstrerer hvordan TypeScript håndhever FormattedCurrencyString-typen ved kompileringstid. Men hvis valutakoden kommer fra en ekstern kilde (f.eks. brukerinput, API-respons), trenger du validering ved kjøretid for å sikre typesikkerhet.
Behovet for kjøretidsvalidering
Selv om TypeScript gir utmerket typesjekking ved kompileringstid, kan det ikke garantere gyldigheten av data mottatt fra eksterne kilder ved kjøretid. Å stole utelukkende på kompileringstidstyper kan føre til uventede feil og sårbarheter.
Vurder følgende scenario:
function processCurrency(currencyString: FormattedCurrencyString) {
// ... noe logikk som antar at strengen er korrekt formatert
}
const userInput = "CAD-50"; // Anta at dette kommer fra brukerinput
// Dette vil kompilere, men vil forårsake en kjøretidsfeil hvis logikken inni
// `processCurrency` er avhengig av formatet.
processCurrency(userInput as FormattedCurrencyString);
I dette tilfellet «caster» vi userInput til FormattedCurrencyString, og omgår dermed TypeScript sine kompileringstidssjekker. Hvis processCurrency er avhengig av at strengen er korrekt formatert, vil den støte på en kjøretidsfeil.
Kjøretidsvalidering bygger bro over dette gapet ved å verifisere at data mottatt ved kjøretid samsvarer med de forventede TypeScript-typene.
Bygge en valideringsmotor for Template Literals
Vi kan bygge en kjøretidsvalideringsmotor ved hjelp av regulære uttrykk og TypeScripts typesystem. Motoren vil ta en template literal-type og en streng som input, og returnere om strengen samsvarer med typen.
Steg 1: Definere en type for kjøretidsvalidering
Først trenger vi en generisk type som kan representere kjøretidsekvivalenten til en template literal-type. Denne typen bør kunne håndtere forskjellige typer template literals, inkludert literaler, unioner og typeparametere.
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;
Denne rekursive typedefinisjonen bryter ned template literal-en i sine bestanddeler og konverterer hver del til et mønster for regulære uttrykk.
Steg 2: Implementere valideringsfunksjonen
Deretter implementerer vi valideringsfunksjonen som tar template literal-typen og strengen som skal valideres som input. Denne funksjonen bruker det regulære uttrykket generert av TemplateLiteralToRegex for å teste strengen.
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 {
// Grunnleggende konvertering for bokstavelige strenger - utvid dette for mer komplekse scenarioer
return templateType.replace(/[.*+?^${}()|\[\]]/g, '\\$&'); // Escape spesielle regex-tegn
}
Denne funksjonen escaper spesielle tegn for regulære uttrykk og lager et regulært uttrykk fra template literal-typen, for deretter å teste strengen mot det regulære uttrykket.
Steg 3: Bruke valideringsmotoren
Nå kan du bruke isValid-funksjonen til å validere strenger mot dine template literal-typer ved kjøretid.
type CurrencyCode = "USD" | "EUR" | "GBP";
type FormattedCurrencyString = `${CurrencyCode}-${number}`;
const userInput1 = "USD-100";
const userInput2 = "CAD-50";
console.log(`'${userInput1}' er gyldig: ${isValid(userInput1, "USD-100" )}`); // true
console.log(`'${userInput2}' er gyldig: ${isValid(userInput2, "USD-100")}`); // false
console.log(`'${userInput1}' er gyldig: ${isValid(userInput1, `USD-${100}`)}`); // true
console.log(`'${userInput2}' er gyldig: ${isValid(userInput2, `USD-${100}`)}`); // false
Dette eksempelet viser hvordan du bruker isValid-funksjonen til å validere brukerinput mot FormattedCurrencyString-typen. Utdataene vil vise om input-strengene anses som gyldige eller ikke, basert på den spesifiserte template literal-en.
Avanserte valideringsscenarioer
Den grunnleggende valideringsmotoren kan utvides til å håndtere mer komplekse scenarioer, som unioner, betingede typer og rekursive typer.
Håndtering av unioner
For å håndtere unioner kan du modifisere TemplateLiteralToRegex-typen for å generere et regulært uttrykk som matcher hvilket som helst av medlemmene i unionen.
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' er en gyldig formatert streng: ${isValidUnionFormattedCurrencyString('USD-100')}`);
console.log(`'CAD-50' er en gyldig formatert streng: ${isValidUnionFormattedCurrencyString('CAD-50')}`);
Håndtering av betingede typer
Betingede typer kan håndteres ved å evaluere betingelsen ved kjøretid og generere forskjellige regulære uttrykk basert på resultatet.
type IsString = T extends string ? true : false;
// Dette eksempelet krever mer avansert logikk og er ikke fullt implementerbart med enkle regulære uttrykk.
// Type guards ved kjøretid tilbyr en mer robust løsning i dette spesifikke scenarioet.
// Koden nedenfor er illustrativ og vil kreve tilpasning for å håndtere komplekse betingede typer.
function isString(value: any): value is string {
return typeof value === 'string';
}
function isValidConditionalType(value: any): boolean {
return isString(value);
}
console.log(`'hello' er en streng: ${isValidConditionalType('hello')}`);
console.log(`123 er en streng: ${isValidConditionalType(123)}`);
Håndtering av rekursive typer
Rekursive typer kan håndteres ved å definere en rekursiv funksjon som genererer mønsteret for det regulære uttrykket. Vær imidlertid forsiktig for å unngå uendelig rekursjon og stack overflow-feil. For dyp rekursjon er iterative tilnærminger med passende begrensninger avgjørende.
Alternativer til regulære uttrykk
Selv om regulære uttrykk er et kraftig verktøy for strengvalidering, kan de være komplekse og vanskelige å vedlikeholde. Andre tilnærminger til kjøretidsvalidering inkluderer:
- Egendefinerte valideringsfunksjoner: Skriv egne funksjoner for å validere spesifikke typer basert på applikasjonens krav.
- Type Guards: Bruk type guards for å snevre inn typen til en variabel ved kjøretid.
- Valideringsbiblioteker: Utnytt eksisterende valideringsbiblioteker som Zod eller Yup for å forenkle valideringsprosessen.
Zod, for eksempel, tilbyr en skjemabasert deklarasjon som oversettes til en kjøretidsvalidering:
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("Gyldig valuta:", validCurrency);
} catch (error) {
console.error("Ugyldig valuta:", error);
}
try {
const invalidCurrency = FormattedCurrencyStringSchema.parse("CAD-50");
console.log("Gyldig valuta:", invalidCurrency); //Dette vil ikke kjøres hvis parse feiler.
} catch (error) {
console.error("Ugyldig valuta:", error);
}
Beste praksis for kjøretidsvalidering
Når du implementerer kjøretidsvalidering, bør du ha følgende beste praksis i tankene:
- Valider ved grensen: Valider data så snart det kommer inn i systemet ditt (f.eks. brukerinput, API-responser).
- Gi klare feilmeldinger: Generer informative feilmeldinger for å hjelpe brukere å forstå hvorfor inputen deres er ugyldig.
- Bruk en konsekvent valideringsstrategi: Ta i bruk en konsekvent valideringsstrategi på tvers av applikasjonen din for å sikre dataintegritet.
- Test valideringslogikken din: Test valideringslogikken grundig for å sikre at den korrekt identifiserer gyldige og ugyldige data.
- Balanser ytelse og sikkerhet: Optimaliser valideringslogikken for ytelse samtidig som du sikrer at den effektivt forhindrer sikkerhetssårbarheter. Unngå altfor komplekse regulære uttrykk som kan føre til tjenestenekt (denial of service).
Hensyn til internasjonalisering
Når du jobber med strengvalidering i en global kontekst, må du ta hensyn til internasjonalisering (i18n) og lokalisering (l10n). Ulike lokasjoner kan ha forskjellige regler for formatering av strenger, som datoer, tall og valutabeløp.
For eksempel kan valutasymbolet for euro (€) vises før eller etter beløpet, avhengig av lokasjonen. Tilsvarende kan desimalskilletegnet være et punktum (.) eller et komma (,).
For å håndtere disse variasjonene kan du bruke internasjonaliseringsbiblioteker som Intl, som tilbyr API-er for formatering og parsing av lokasjonsfølsomme data. For eksempel kan du tilpasse det forrige eksempelet for å håndtere forskjellige valutaformater:
function isValidCurrencyString(currencyString: string, locale: string): boolean {
try {
const formatter = new Intl.NumberFormat(locale, { style: 'currency', currency: currencyString.substring(0,3) }); //Veldig grunnleggende eksempel
//Forsøk å parse valutaen med formatter. Dette eksempelet er med vilje veldig enkelt.
return true;
} catch (error) {
return false;
}
}
console.log(`USD-100 er gyldig for en-US: ${isValidCurrencyString('USD-100', 'en-US')}`);
console.log(`EUR-100 er gyldig for fr-FR: ${isValidCurrencyString('EUR-100', 'fr-FR')}`);
Dette kodeeksempelet gir et grunnleggende eksempel. Korrekt internasjonalisering krever mer grundig håndtering, og kan potensielt benytte eksterne biblioteker eller API-er som er spesifikt designet for valutaformatering og -validering på tvers av forskjellige lokasjoner.
Konklusjon
Kjøretidsvalidering er en essensiell del av å bygge robuste og pålitelige TypeScript-applikasjoner. Ved å kombinere TypeScript sine template literal-typer med regulære uttrykk eller alternative valideringsmetoder, kan du skape en kraftig motor for å verifisere gyldigheten av strenger ved kjøretid.
Denne tilnærmingen forbedrer typesikkerheten, forhindrer uventede feil og øker den generelle kvaliteten på koden din. Etter hvert som du bygger mer komplekse applikasjoner, bør du vurdere å innlemme kjøretidsvalidering for å sikre at dataene dine samsvarer med de forventede typene og formatene.
Videre utforskning
- Utforsk avanserte teknikker for regulære uttrykk for mer komplekse valideringsscenarioer.
- Undersøk valideringsbiblioteker som Zod og Yup for skjemabasert validering.
- Vurder å bruke kodegenereringsteknikker for å automatisk generere valideringsfunksjoner fra TypeScript-typer.
- Studer internasjonaliseringsbiblioteker og API-er for å håndtere lokasjonsfølsomme data.