Išsamus vadovas apie TypeScript patvirtinimo funkcijas. Sužinokite, kaip panaikinti atotrūkį tarp kompiliavimo ir vykdymo laiko, tikrinti duomenis ir rašyti saugesnį, patikimesnį kodą su praktiniais pavyzdžiais.
TypeScript patvirtinimo funkcijos: išsamus vadovas apie tipų saugumą vykdymo metu
Interneto svetainių kūrimo pasaulyje sutartis tarp jūsų kodo lūkesčių ir gaunamų duomenų realybės dažnai yra trapi. „TypeScript“ sukėlė revoliuciją, kaip mes rašome „JavaScript“, suteikdama galingą statinę tipų sistemą, kuri pagauna daugybę klaidų, dar joms nepasiekus produkcinės aplinkos. Tačiau šis saugumo tinklas pirmiausia egzistuoja kompiliavimo metu. Kas nutinka, kai jūsų gražiai tipizuota programa vykdymo metu gauna netvarkingus, nenuspėjamus duomenis iš išorinio pasaulio? Būtent čia „TypeScript“ patvirtinimo funkcijos tampa nepakeičiamu įrankiu kuriant tikrai patikimas programas.
Šis išsamus vadovas nuodugniai supažindins jus su patvirtinimo funkcijomis. Išnagrinėsime, kodėl jos yra būtinos, kaip jas sukurti nuo nulio ir kaip jas pritaikyti įprastose, realaus pasaulio situacijose. Baigę būsite pasirengę rašyti kodą, kuris yra ne tik tipų požiūriu saugus kompiliavimo metu, bet ir atsparus bei nuspėjamas vykdymo metu.
Didysis atotrūkis: kompiliavimo laikas ir vykdymo laikas
Norėdami iš tikrųjų įvertinti patvirtinimo funkcijas, pirmiausia turime suprasti pagrindinį iššūkį, kurį jos sprendžia: atotrūkį tarp „TypeScript“ kompiliavimo laiko pasaulio ir „JavaScript“ vykdymo laiko pasaulio.
„TypeScript“ kompiliavimo laiko rojus
Kai rašote „TypeScript“ kodą, dirbate programuotojo rojuje. „TypeScript“ kompiliatorius (tsc
) veikia kaip budrus asistentas, analizuojantis jūsų kodą pagal jūsų apibrėžtus tipus. Jis tikrina:
- Ar funkcijoms neperduodami neteisingi tipai.
- Ar nesikreipiama į savybes, kurių objekte nėra.
- Ar neiškviečiamas kintamasis, kuris gali būti
null
arbaundefined
.
Šis procesas vyksta prieš jūsų kodui pradedant veikti. Galutinis rezultatas yra grynas „JavaScript“, iš kurio pašalintos visos tipų anotacijos. Įsivaizduokite „TypeScript“ kaip detalų pastato architektūrinį brėžinį. Jis užtikrina, kad visi planai yra tvarkingi, išmatavimai teisingi, o konstrukcijos vientisumas garantuotas popieriuje.
„JavaScript“ vykdymo laiko realybė
Kai jūsų „TypeScript“ kodas sukompiliuojamas į „JavaScript“ ir paleidžiamas naršyklėje ar „Node.js“ aplinkoje, statiniai tipai dingsta. Jūsų kodas dabar veikia dinamiškame, nenuspėjamame vykdymo laiko pasaulyje. Jis turi susidoroti su duomenimis iš šaltinių, kurių negali kontroliuoti, pavyzdžiui:
- API atsakymai: Serverio paslauga gali netikėtai pakeisti savo duomenų struktūrą.
- Vartotojo įvestis: Duomenys iš HTML formų visada traktuojami kaip eilutė, nepriklausomai nuo įvesties tipo.
- Vietinė saugykla (Local Storage): Duomenys, gauti iš
localStorage
, visada yra eilutė ir juos reikia išanalizuoti. - Aplinkos kintamieji: Jie dažnai yra eilutės ir gali visai neegzistuoti.
Panaudojant mūsų analogiją, vykdymo laikas yra statybų aikštelė. Brėžinys buvo tobulas, bet pristatytos medžiagos (duomenys) gali būti netinkamo dydžio, netinkamo tipo arba tiesiog trūkti. Jei bandysite statyti su šiomis netinkamomis medžiagomis, jūsų konstrukcija sugrius. Būtent čia atsiranda vykdymo laiko klaidos, dažnai sukeliančios programos gedimus ir klaidas, tokias kaip „Cannot read properties of undefined“.
Pristatome patvirtinimo funkcijas: atotrūkio panaikinimas
Taigi, kaip mes galime pritaikyti savo „TypeScript“ brėžinį nenuspėjamoms vykdymo laiko medžiagoms? Mums reikia mechanizmo, kuris galėtų patikrinti duomenis *jiems atvykstant* ir patvirtinti, kad jie atitinka mūsų lūkesčius. Būtent tai ir daro patvirtinimo funkcijos.
Kas yra patvirtinimo funkcija?
Patvirtinimo funkcija yra speciali funkcija „TypeScript“, atliekanti du kritiškai svarbius tikslus:
- Tikrinimas vykdymo metu: Ji atlieka reikšmės ar sąlygos patikrinimą. Jei patikrinimas nepavyksta, ji išmeta klaidą, nedelsiant sustabdydama to kodo kelio vykdymą. Tai neleidžia netinkamiems duomenims plisti toliau jūsų programoje.
- Tipo susiaurinimas kompiliavimo metu: Jei patikrinimas sėkmingas (t. y. klaida neišmetama), ji signalizuoja „TypeScript“ kompiliatoriui, kad reikšmės tipas dabar yra konkretesnis. Kompiliatorius pasitiki šiuo patvirtinimu ir leidžia jums naudoti reikšmę kaip patvirtintą tipą likusioje jos apimties dalyje.
Magija slypi funkcijos signatūroje, kuri naudoja asserts
raktažodį. Yra dvi pagrindinės formos:
asserts condition [is type]
: Ši forma patvirtina, kad tam tikracondition
yra teigiama (truthy). Pasirinktinai galite įtrauktiis type
(tipo predikatą), kad taip pat susiaurintumėte kintamojo tipą.asserts this is type
: Tai naudojama klasių metoduose, norint patvirtintithis
konteksto tipą.
Svarbiausia yra „išmesti klaidą nesėkmės atveju“ elgesys. Skirtingai nuo paprastos if
patikros, patvirtinimas deklaruoja: „Ši sąlyga *privalo* būti teisinga, kad programa galėtų tęsti darbą. Jei ne, tai yra išskirtinė būsena, ir mes turėtume nedelsiant sustoti.“
Pirmosios patvirtinimo funkcijos kūrimas: praktinis pavyzdys
Pradėkime nuo vienos iš labiausiai paplitusių problemų „JavaScript“ ir „TypeScript“: susidūrimo su galimai null
arba undefined
reikšmėmis.
Problema: nepageidaujamos `null` reikšmės
Įsivaizduokite funkciją, kuri priima nebūtiną vartotojo objektą ir nori išvesti vartotojo vardą. „TypeScript“ griežtos `null` patikros teisingai įspės mus apie galimą klaidą.
interface User {
name: string;
email: string;
}
function logUserName(user: User | undefined) {
// 🚨 TypeScript klaida: „user“ gali būti „undefined“.
console.log(user.name.toUpperCase());
}
Standartinis būdas tai ištaisyti yra su if
patikra:
function logUserName(user: User | undefined) {
if (user) {
// Šiame bloke TypeScript žino, kad „user“ tipas yra „User“.
console.log(user.name.toUpperCase());
} else {
console.error('User is not provided.');
}
}
Tai veikia, bet kas, jei `user` buvimas `undefined` yra nepataisoma klaida šiame kontekste? Mes nenorime, kad funkcija tyliai tęstų darbą. Mes norime, kad ji garsiai sugestų. Tai veda prie pasikartojančių apsauginių sąlygų.
Sprendimas: `assertIsDefined` patvirtinimo funkcija
Sukurkime daugkartinio naudojimo patvirtinimo funkciją, kad elegantiškai išspręstume šį modelį.
// Mūsų daugkartinio naudojimo patvirtinimo funkcija
function assertIsDefined<T>(value: T, message: string = "Value is not defined"): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error(message);
}
}
// Panaudokime ją!
interface User {
name: string;
email: string;
}
function logUserName(user: User | undefined) {
assertIsDefined(user, "User object must be provided to log name.");
// Nėra klaidos! TypeScript dabar žino, kad „user“ tipas yra „User“.
// Tipas buvo susiaurintas iš „User | undefined“ į „User“.
console.log(user.name.toUpperCase());
}
// Naudojimo pavyzdys:
const validUser = { name: 'Alice', email: 'alice@example.com' };
logUserName(validUser); // Išveda „ALICE“
const invalidUser = undefined;
try {
logUserName(invalidUser); // Išmeta klaidą: „User object must be provided to log name.“
} catch (error) {
console.error(error.message);
}
Patvirtinimo signatūros analizė
Išnagrinėkime signatūrą: asserts value is NonNullable<T>
asserts
: Tai specialus „TypeScript“ raktažodis, paverčiantis šią funkciją patvirtinimo funkcija.value
: Tai nurodo pirmąjį funkcijos parametrą (mūsų atveju, kintamąjį pavadinimu `value`). Jis nurodo „TypeScript“, kurio kintamojo tipą reikia susiaurinti.is NonNullable<T>
: Tai yra tipo predikatas. Jis nurodo kompiliatoriui, kad jei funkcija neišmeta klaidos, `value` tipas dabar yraNonNullable<T>
. Pagalbinis „TypeScript“ tipasNonNullable
pašalinanull
irundefined
iš tipo.
Praktiniai patvirtinimo funkcijų panaudojimo atvejai
Dabar, kai suprantame pagrindus, panagrinėkime, kaip pritaikyti patvirtinimo funkcijas sprendžiant įprastas, realaus pasaulio problemas. Jos yra galingiausios jūsų programos ribose, kur išoriniai, netipizuoti duomenys patenka į jūsų sistemą.
1 panaudojimo atvejis: API atsakymų tikrinimas
Tai, be abejonės, yra svarbiausias panaudojimo atvejis. Duomenys iš fetch
užklausos yra iš prigimties nepatikimi. „TypeScript“ teisingai tipizuoja `response.json()` rezultatą kaip `Promise
Scenarijus
Mes gauname vartotojo duomenis iš API. Tikimės, kad jie atitiks mūsų `User` sąsają, bet negalime būti tikri.
interface User {
id: number;
name: string;
email: string;
}
// Įprasta tipo apsauga (grąžina loginę reikšmę)
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data && typeof (data as any).id === 'number' &&
'name' in data && typeof (data as any).name === 'string' &&
'email' in data && typeof (data as any).email === 'string'
);
}
// Mūsų nauja patvirtinimo funkcija
function assertIsUser(data: unknown): asserts data is User {
if (!isUser(data)) {
throw new TypeError('Invalid User data received from API.');
}
}
async function fetchAndProcessUser(userId: number) {
const response = await fetch(`https://api.example.com/users/${userId}`);
const data: unknown = await response.json();
// Patvirtinkite duomenų struktūrą ties riba
assertIsUser(data);
// Nuo šio momento „data“ yra saugiai tipizuotas kaip „User“.
// Daugiau nereikia „if“ patikrų ar tipo keitimo!
console.log(`Processing user: ${data.name.toUpperCase()} (${data.email})`);
}
fetchAndProcessUser(1);
Kodėl tai galinga: Iškviesdami `assertIsUser(data)` iškart po atsakymo gavimo, sukuriame „saugumo vartus“. Bet koks tolesnis kodas gali drąsiai traktuoti `data` kaip `User`. Tai atsieja tikrinimo logiką nuo verslo logikos, todėl kodas tampa daug švaresnis ir lengviau skaitomas.
2 panaudojimo atvejis: užtikrinimas, kad aplinkos kintamieji egzistuoja
Serverio pusės programos (pvz., „Node.js“) labai priklauso nuo aplinkos kintamųjų konfigūracijai. Kreipiantis į `process.env.MY_VAR` gaunamas tipas `string | undefined`. Tai verčia jus tikrinti jo egzistavimą visur, kur jį naudojate, o tai yra varginantis ir klaidų keliantis procesas.
Scenarijus
Mūsų programai paleisti reikalingas API raktas ir duomenų bazės URL iš aplinkos kintamųjų. Jei jų trūksta, programa negali veikti ir turėtų nedelsiant sustoti su aiškiu klaidos pranešimu.
// Pagalbiniame faile, pvz., 'config.ts'
export function getEnvVar(key: string): string {
const value = process.env[key];
if (value === undefined) {
throw new Error(`FATAL: Environment variable ${key} is not set.`);
}
return value;
}
// Galingesnė versija, naudojanti patvirtinimus
function assertEnvVar(key: string): asserts key is keyof NodeJS.ProcessEnv {
if (process.env[key] === undefined) {
throw new Error(`FATAL: Environment variable ${key} is not set.`);
}
}
// Jūsų programos įvesties taške, pvz., 'index.ts'
function startServer() {
// Atlikite visus patikrinimus paleidimo metu
assertEnvVar('API_KEY');
assertEnvVar('DATABASE_URL');
const apiKey = process.env.API_KEY;
const dbUrl = process.env.DATABASE_URL;
// TypeScript dabar žino, kad apiKey ir dbUrl yra eilutės, o ne „string | undefined“.
// Jūsų programa garantuotai turės reikiamą konfigūraciją.
console.log('API Key length:', apiKey.length);
console.log('Connecting to DB:', dbUrl.toLowerCase());
// ... likusi serverio paleidimo logika
}
startServer();
Kodėl tai galinga: Šis modelis vadinamas „fail-fast“ (greitas gedimas). Jūs patikrinate visas kritines konfigūracijas vieną kartą, pačioje programos gyvavimo ciklo pradžioje. Jei kyla problema, ji nedelsiant sugenda su aprašomuoju klaidos pranešimu, kurį daug lengviau derinti nei paslaptingą gedimą, kuris įvyksta vėliau, kai pagaliau panaudojamas trūkstamas kintamasis.
3 panaudojimo atvejis: darbas su DOM
Kai darote užklausą DOM, pavyzdžiui, su `document.querySelector`, rezultatas yra `Element | null`. Jei esate tikri, kad elementas egzistuoja (pvz., pagrindinis programos šakninis `div`), nuolatinis `null` tikrinimas gali būti varginantis.
Scenarijus
Turime HTML failą su `
`, ir mūsų scenarijus turi prie jo prijungti turinį. Mes žinome, kad jis egzistuoja.
// Iš naujo naudojame mūsų bendrąją patvirtinimo funkciją
function assertIsDefined<T>(value: T, message: string = "Value is not defined"): asserts value is NonNullable<T> {
if (value === undefined || value === null) {
throw new Error(message);
}
}
// Specifiškesnis patvirtinimas DOM elementams
function assertQuerySelector<T extends Element>(selector: string, constructor?: new () => T): T {
const element = document.querySelector(selector);
assertIsDefined(element, `FATAL: Element with selector '${selector}' not found in the DOM.`);
// Pasirinktinai: patikrinkite, ar tai tinkamo tipo elementas
if (constructor && !(element instanceof constructor)) {
throw new TypeError(`Element '${selector}' is not an instance of ${constructor.name}`);
}
return element as T;
}
// Naudojimas
const appRoot = document.querySelector('#app-root');
assertIsDefined(appRoot, 'Could not find the main application root element.');
// Po patvirtinimo, appRoot tipas yra „Element“, o ne „Element | null“.
appRoot.innerHTML = 'Hello, World!
';
// Naudojant specifiškesnę pagalbinę funkciją
const submitButton = assertQuerySelector<HTMLButtonElement>('#submit-btn', HTMLButtonElement);
// „submitButton“ dabar yra teisingai tipizuotas kaip HTMLButtonElement
submitButton.disabled = true;
Kodėl tai galinga: Tai leidžia jums išreikšti invariantą – sąlygą, kurią žinote esant teisingą – apie jūsų aplinką. Tai pašalina triukšmingą `null` tikrinimo kodą ir aiškiai dokumentuoja scenarijaus priklausomybę nuo konkrečios DOM struktūros. Jei struktūra pasikeičia, gaunate nedelsiant aiškią klaidą.
Patvirtinimo funkcijos prieš alternatyvas
Labai svarbu žinoti, kada naudoti patvirtinimo funkciją, o kada kitas tipo susiaurinimo technikas, tokias kaip tipų apsaugos ar tipų keitimas.
Metodas | Sintaksė | Elgesys nesėkmės atveju | Geriausiai tinka |
---|---|---|---|
Tipų apsaugos | value is Type |
Grąžina false |
Valdymo srautui (if/else ). Kai yra galiojantis, alternatyvus kodo kelias „nelaimingam“ atvejui. Pvz., „Jei tai eilutė, apdorok ją; kitu atveju, naudok numatytąją reikšmę.“ |
Patvirtinimo funkcijos | asserts value is Type |
Išmeta Error |
Invariantų užtikrinimui. Kai sąlyga privalo būti teisinga, kad programa galėtų tęsti darbą teisingai. „Nelaimingas“ kelias yra nepataisoma klaida. Pvz., „API atsakymas privalo būti User objektas.“ |
Tipų keitimas (casting) | value as Type |
Jokio poveikio vykdymo metu | Retais atvejais, kai jūs, programuotojas, žinote daugiau nei kompiliatorius ir jau atlikote būtinus patikrinimus. Tai nesuteikia jokio saugumo vykdymo metu ir turėtų būti naudojama saikingai. Pernelyg dažnas naudojimas yra „kodo kvapas“. |
Pagrindinė gairė
Paklauskite savęs: „Kas turėtų nutikti, jei ši patikra nepavyks?“
- Jei yra teisėtas alternatyvus kelias (pvz., rodyti prisijungimo mygtuką, jei vartotojas neautentifikuotas), naudokite tipo apsaugą su
if/else
bloku. - Jei nepavykusi patikra reiškia, kad jūsų programa yra netinkamoje būsenoje ir negali saugiai tęsti darbo, naudokite patvirtinimo funkciją.
- Jei jūs ignoruojate kompiliatorių be patikros vykdymo metu, jūs naudojate tipo keitimą. Būkite labai atsargūs.
Pažangūs modeliai ir geriausios praktikos
1. Sukurkite centrinę patvirtinimo funkcijų biblioteką
Neišmėtykite patvirtinimo funkcijų po visą savo kodą. Centralizuokite jas specializuotame pagalbiniame faile, pavyzdžiui, src/utils/assertions.ts
. Tai skatina pakartotinį naudojimą, nuoseklumą ir leidžia lengvai rasti bei testuoti jūsų tikrinimo logiką.
// src/utils/assertions.ts
export function assert(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}
export function assertIsDefined<T>(value: T): asserts value is NonNullable<T> {
assert(value !== null && value !== undefined, 'This value must be defined.');
}
export function assertIsString(value: unknown): asserts value is string {
assert(typeof value === 'string', 'This value must be a string.');
}
// ... ir taip toliau.
2. Išmeskite prasmingas klaidas
Klaidos pranešimas iš nepavykusio patvirtinimo yra jūsų pirmasis įkaltis derinimo metu. Padarykite jį vertingą! Bendrinis pranešimas, toks kaip „Patvirtinimas nepavyko“, nėra naudingas. Vietoj to, pateikite kontekstą:
- Kas buvo tikrinama?
- Kokia buvo laukiama reikšmė/tipas?
- Kokia buvo gauta reali reikšmė/tipas? (Būkite atsargūs, kad neišvestumėte jautrių duomenų).
function assertIsUser(data: unknown): asserts data is User {
if (!isUser(data)) {
// Blogai: throw new Error('Netinkami duomenys');
// Gerai:
throw new TypeError(`Expected data to be a User object, but received ${JSON.stringify(data)}`);
}
}
3. Atsižvelkite į našumą
Patvirtinimo funkcijos yra vykdymo laiko patikros, o tai reiškia, kad jos naudoja procesoriaus ciklus. Tai yra visiškai priimtina ir pageidautina jūsų programos ribose (API įvestis, konfigūracijos įkėlimas). Tačiau venkite dėti sudėtingus patvirtinimus į našumui kritiškas kodo dalis, tokias kaip trumpas ciklas, kuris vykdomas tūkstančius kartų per sekundę. Naudokite jas ten, kur patikros kaina yra nereikšminga, palyginti su atliekama operacija (pavyzdžiui, tinklo užklausa).
Išvada: rašykite kodą užtikrintai
„TypeScript“ patvirtinimo funkcijos yra daugiau nei nišinė funkcija; jos yra pagrindinis įrankis rašant patikimas, produkcinio lygio programas. Jos suteikia jums galimybę panaikinti kritinį atotrūkį tarp kompiliavimo laiko teorijos ir vykdymo laiko realybės.
Prisijaukinę patvirtinimo funkcijas, galite:
- Užtikrinti invariantus: Formaliai deklaruoti sąlygas, kurios privalo būti teisingos, padarydami jūsų kodo prielaidas aiškias.
- Greitai ir garsiai sugesti: Pagauti duomenų vientisumo problemas prie šaltinio, užkertant kelią joms sukelti subtilias ir sunkiai derinamas klaidas vėliau.
- Pagerinti kodo aiškumą: Pašalinti įdėtas
if
patikras ir tipų keitimus, todėl verslo logika tampa švaresnė, tiesiškesnė ir savaime dokumentuojanti. - Padidinti pasitikėjimą: Rašyti kodą užtikrintai, kad jūsų tipai yra ne tik pasiūlymai kompiliatoriui, bet ir aktyviai užtikrinami, kai kodas vykdomas.
Kitą kartą, kai gausite duomenis iš API, skaitysite konfigūracijos failą ar apdorosite vartotojo įvestį, ne tik pakeiskite tipą ir tikėkitės geriausio. Patvirtinkite tai. Sukurkite saugumo vartus savo sistemos pakraštyje. Jūsų ateities aš – ir jūsų komanda – padėkos jums už parašytą patikimą, nuspėjamą ir atsparų kodą.