22 β React va Node bilan TypeScript¶
β¬ οΈ Oldingi: 21 β Tashqi kutubxonalar va @types Β· π README Β· Keyingi: 23 β Migratsiya: JS'dan TypeScript'ga β‘οΈ
Bu bobda: shu paytgacha o'rgangan tiplarni real freymvorklarda qo'llaymiz. Frontend tomonda React komponentlarining
props'iniinterfacebilan tiplash,useState<T>, event handler tiplari (onChange,onClick),childrentipi vaReact.FCmunozarasini ko'ramiz. Backend tomonda Node + Express'ni@types/nodeva@types/expressbilan tiplab,req/res'ni xavfsiz qilamiz, environment (process.env)'ni tiplaymiz vatsxbilan ishga tushiramiz. Bu bob β ikkala ekotizimga amaliy ko'prik: chuqur React yoki Node darsligi emas, balki "o'rgangan tiplarimni qayerda ishlataman?" degan savolga javob.
Muammo¶
Siz 21 bob davomida tiplarni o'rgandingiz: interface, union, generic, narrowing. Lekin hozircha hammasi "yakka" misollarda edi. Real ish esa freymvorkda: brauzerda React, serverda Node. Va aynan shu yerda tiplangan kod eng ko'p foyda keltiradi, chunki freymvorkda xatolik narxi yuqori β props noto'g'ri uzatilsa butun komponent ishlamaydi, API javobi noto'g'ri o'qilsa server qulaydi.
JavaScript'da React komponenti shunday yoziladi va hech kim sizni ogohlantirmaydi:
function Salom(props) {
return <h1>Salom, {props.ism}!</h1>;
}
// Boshqa joyda β xato, lekin JS indamaydi:
<Salom name="Aziz" /> // "ism" emas, "name" yozdik β undefined chiqadi
<Salom /> // ism umuman berilmadi
Natija ekranda Salom, undefined! β dastur ishlaydi-yu, buzuq. Bu xato runtime'da, foydalanuvchi qo'lida chiqadi. Backend tomonda ham xuddi shunday: req.body.summa deb yozasiz, lekin u stringmi, numbermi β bilmaysiz, va summa * 2 kutilmagan natija beradi.
TypeScript bu ikkala muammoni ham yopadi. Komponent qaysi props'ni kutishini shartnoma (interface) bilan e'lon qilasiz, va noto'g'ri ishlatilsa kod kompilyatsiya'dan o'tmaydi β xato siz uni yozayotgan paytda, muharrirda chiqadi:
interface SalomProps {
ism: string;
}
function Salom(props: SalomProps) {
return <h1>Salom, {props.ism}!</h1>;
}
// Endi:
<Salom ism="Aziz" />; // β
to'g'ri
<Salom name="Aziz" />; // β Xato: 'name' SalomProps'da yo'q, 'ism' kerak
<Salom />; // β Xato: 'ism' propi yetishmayapti
Keling, avval frontend (React), keyin backend (Node/Express) tomonni qadama-qadam ochamiz.
Bu bob React va Node'ni noldan o'rgatmaydi β komponent, JSX, server, route nima ekanini bilasiz deb hisoblaydi. Bu yerda diqqat tiplashda. Chuqurroq freymvork bilimi uchun alohida darsliklar bor.
A qism β React + TypeScript¶
.tsx fayl va sozlash¶
React komponentlari JSX yozadi (<div>...</div> HTML'ga o'xshash sintaksis JavaScript ichida). TypeScript JSX'ni faqat .tsx kengaytmali faylda tushunadi (oddiy .ts'da emas). tsconfig.json'da jsx opsiyasi yoqilgan bo'lishi kerak:
{
"compilerOptions": {
"target": "es2020",
"jsx": "react-jsx",
"strict": true,
"moduleResolution": "bundler"
}
}
π "jsx": "react-jsx" β zamonaviy variant (React 17+). Bunda har faylda import React from "react" yozish shart emas; TypeScript JSX'ni avtomatik to'g'ri o'giradi. Eski loyihalarda "jsx": "react" uchraydi β u har faylda React'ni import qilishni talab qiladi.
π‘ Amalda React loyihasini noldan o'zingiz sozlamaysiz: Vite (npm create vite@latest -- --template react-ts) yoki shunga o'xshash vosita tsconfig.json va @types/react'ni siz uchun tayyorlaydi. Sizning vazifangiz β komponentlarni to'g'ri tiplash.
Props'ni interface bilan tiplash¶
Komponent props'i β oddiy obyekt. Demak uni biz 05-bobda o'rgangan interface bilan tasvirlaymiz. Bu React + TS'ning eng ko'p ishlatiladigan qolipi:
interface TugmaProps {
matn: string;
rang: "kok" | "qizil"; // union literal β faqat shu ikki qiymat
ochiq?: boolean; // optional β berilmasa bo'ladi
}
function Tugma({ matn, rang, ochiq = true }: TugmaProps) {
return (
<button disabled={!ochiq} className={rang}>
{matn}
</button>
);
}
E'tibor bering: props'ni to'g'ridan-to'g'ri destructuring ({ matn, rang }) bilan oldik va tipni shu joyda berdik. rang uchun union literal ishlatdik β endi rang="yashil" yozsangiz kod o'tmaydi, faqat "kok" yoki "qizil".
Endi komponentni ishlatganda TypeScript hammasini tekshiradi:
<Tugma matn="Saqlash" rang="kok" />; // β
<Tugma matn="O'chirish" rang="qizil" ochiq={false} />; // β
<Tugma matn="Yuborish" rang="yashil" />; // β Xato: "yashil" β "kok" | "qizil" emas
<Tugma rang="kok" />; // β Xato: 'matn' propi yetishmayapti
<Tugma matn={42} rang="kok" />; // β Xato: number β string'ga mos kelmaydi
π ? bilan belgilangan ochiq β optional. Uni bermasangiz default qiymati (= true) ishlaydi. Optional bo'lmagan matn va rang'ni esa berish shart.
useState<T> β tiplangan holat¶
useState β generic funksiya (11-bob). U sizga [qiymat, o'zgartiruvchi] juftligini qaytaradi, va qiymat tipi muhim. Ko'p hollarda TypeScript tipni o'zi aniqlaydi (inference, 03-bob):
import { useState } from "react";
function Hisoblagich() {
const [son, setSon] = useState(0); // TS biladi: son β number
// son.toUpperCase(); // β bo'lardi β number'da bunday metod yo'q
return (
<button onClick={() => setSon(son + 1)}>
Bosildi: {son}
</button>
);
}
Bu yerda useState(0) boshlang'ich qiymati 0 bo'lgani uchun TypeScript son'ni number deb biladi β generic argument yozishimiz shart emas.
Ammo ba'zan boshlang'ich qiymat kelajakdagi tipni ko'rsatmaydi. Klassik holat β boshida null, keyin obyekt keladi. Bunda generic argumentni aniq yozish kerak:
import { useState } from "react";
interface Foydalanuvchi {
id: number;
ism: string;
}
function Profil() {
// Boshida foydalanuvchi yo'q (null), keyin yuklanadi:
const [user, setUser] = useState<Foydalanuvchi | null>(null);
function yukla() {
setUser({ id: 1, ism: "Aziz" }); // β
Foydalanuvchi'ga mos
}
// user null bo'lishi mumkin β TS narrowing'ni talab qiladi (08-bob):
return <p>{user ? user.ism : "Yuklanmoqda..."}</p>;
}
π Agar useState(null) deb yozsangiz (generic'siz), TypeScript tipni null deb qotirib qo'yadi β keyin setUser({...}) qilolmaysiz, chunki obyekt null emas. Shuning uchun "hozir null, keyin boshqa narsa" holatida useState<T | null>(null) qolipi ishlatiladi.
π‘ setUser ham tiplangan: faqat Foydalanuvchi | null qabul qiladi. setUser("matn") yozsangiz β kompilyatsiya xatosi. Demak holatni "buzuq qiymat" bilan to'ldirib qo'yib bo'lmaydi.
Event tiplari β onChange, onClick¶
JavaScript'da event β qora quti: nima borligini bilmaysiz. TypeScript'da React har event uchun aniq tip beradi. Eng ko'p uchraydigani β input o'zgarishi:
import { useState } from "react";
function Forma() {
const [matn, setMatn] = useState("");
function ozgardi(e: React.ChangeEvent<HTMLInputElement>) {
setMatn(e.target.value); // e.target β HTMLInputElement, .value bor
}
return <input value={matn} onChange={ozgardi} />;
}
React.ChangeEvent<HTMLInputElement> β generic event tipi: "input elementidagi o'zgarish hodisasi". Endi e.target.value (string)'ni xavfsiz o'qiysiz; xato maydon nomi yozsangiz darrov bilinadi.
π Eng ko'p ishlatiladigan event tiplari:
| Hodisa | Tip |
|---|---|
| Input/textarea o'zgarishi | React.ChangeEvent<HTMLInputElement> |
| Tugma bosilishi | React.MouseEvent<HTMLButtonElement> |
| Forma yuborilishi | React.FormEvent<HTMLFormElement> |
| Klaviatura | React.KeyboardEvent<HTMLInputElement> |
π‘ Eng oson yo'l β handler'ni to'g'ridan-to'g'ri JSX ichida yozish. Unda TypeScript event tipini o'zi aniqlaydi (inference), siz yozishingiz shart emas:
<input onChange={(e) => setMatn(e.target.value)} />
// e β React.ChangeEvent<HTMLInputElement> deb avtomatik aniqlandi
Tipni faqat handler'ni alohida funksiya sifatida ajratganingizda qo'lda yozasiz β chunki u JSX kontekstidan tashqarida, TS unga "qaysi event?" degan savolga javob topa olmaydi.
children tipi¶
Ko'p komponentlar ichiga boshqa elementlarni "o'rab oladi" β masalan karta, modal, layout. Ichkaridagi narsa children propi orqali keladi va uning tipi β React.ReactNode:
import { ReactNode } from "react";
interface KartaProps {
sarlavha: string;
children: ReactNode; // ichiga istalgan React mazmuni
}
function Karta({ sarlavha, children }: KartaProps) {
return (
<div className="karta">
<h2>{sarlavha}</h2>
<div>{children}</div>
</div>
);
}
React.ReactNode β eng keng tip: matn, son, JSX element, ularning massivi, null β hammasi mos. Komponentni ichiga mazmun solib ishlatasiz:
π <Karta>...</Karta> ichidagi hamma narsa avtomatik children propiga tushadi. Siz uni qo'lda children=... deb yozmaysiz.
π‘ Faqat matnli children kutsangiz children: string ham yozish mumkin β unda komponent ichiga JSX qo'ysangiz xato chiqadi. Lekin amalda ReactNode ancha moslashuvchan va shuning uchun standart tanlov.
React.FC β kerakmi?¶
Ko'p eski misollarda komponentni shunday tiplaydi:
import { FC } from "react";
interface SalomProps {
ism: string;
}
const Salom: FC<SalomProps> = ({ ism }) => {
return <h1>Salom, {ism}!</h1>;
};
FC (FunctionComponent) β komponent tipi; children'ni o'zi qo'shgani uchun bir paytlar qulay edi. Ammo bugungi tavsiya β FC'siz, props'ni to'g'ridan-to'g'ri tiplash:
interface SalomProps {
ism: string;
}
// β
Tavsiya etilgan zamonaviy uslub:
function Salom({ ism }: SalomProps) {
return <h1>Salom, {ism}!</h1>;
}
π Nega FC'dan voz kechilyapti? Eski FC children'ni avtomatik (va majburiy bo'lmagan holda) qo'shar edi β bu noaniqlik tug'dirardi: komponent children kutmasa ham, uni berish mumkin ko'rinardi. To'g'ridan-to'g'ri tiplashda children'ni faqat kerak bo'lsa o'zingiz interface'ga qo'shasiz β aniq va ochiq. Ikkala uslub ham ishlaydi, lekin yangi kodda function + props interface afzal.
π‘ Generic komponent (masalan, har xil tipdagi ro'yxatni ko'rsatadigan List<T>) yozmoqchi bo'lsangiz β FC bilan generic noqulay. To'g'ridan-to'g'ri funksiya esa generic'ni tabiiy qabul qiladi: function List<T>(props: ListProps<T>) {...}.
B qism β Node + Express + TypeScript¶
Endi serverga o'tamiz. Backend'da TypeScript foydasi boshqacha, lekin teng darajada muhim: kelayotgan so'rov (req) va ketayotgan javob (res) tiplangan bo'ladi, environment o'zgaruvchilari tekshiriladi, va API javobining shakli (interface) kod bo'ylab bir xil saqlanadi.
Node tiplari: @types/node¶
Node'ning o'zi (process, fs, path, Buffer) JavaScript'da yozilgan β tip ma'lumoti yo'q. Shuning uchun @types/node paketini o'rnatish kerak (21-bobda ko'rgan @types/... qolipi):
Bundan keyin process.env, process.argv, __dirname kabilar tiplangan bo'ladi. tsconfig.json'da Node uchun odatda:
{
"compilerOptions": {
"target": "es2022",
"module": "nodenext",
"moduleResolution": "nodenext",
"strict": true,
"esModuleInterop": true
}
}
π tsx β TypeScript faylni kompilyatsiyasiz to'g'ridan-to'g'ri ishga tushiradigan vosita (ichida tez kompilyator bor). Ishga tushirish: npx tsx server.ts. Bu eski ts-node'ga zamonaviy almashtiruv β sozlash osonroq va tezroq. Ishlab chiqarish (production) uchun esa odatda avval tsc bilan .js'ga kompilyatsiya qilib, keyin node'da ishlatasiz.
Express'ni tiplash: @types/express¶
Express'ning o'zi ham JS'da. Tiplar uchun:
Endi req/res tiplangan. Eng oddiy tiplangan server:
import express, { Request, Response } from "express";
const app = express();
app.use(express.json()); // JSON body'ni o'qish uchun
app.get("/salom", (req: Request, res: Response) => {
res.json({ xabar: "Salom, dunyo!" });
});
app.listen(3000, () => {
console.log("Server 3000-portda ishlamoqda");
});
req: Request, res: Response β @types/express'dan keladigan tiplar. res.json(...), res.status(...), req.body β hammasi tiplangan. Noto'g'ri metod nomi (res.jsno(...)) yozsangiz darrov xato chiqadi.
req ichini tiplash: generic'lar¶
Request'ning kuchli tomoni β u generic. Marshrut parametrlari (/user/:id'dagi id), so'rov tanasi (req.body) va query (?sahifa=2) tiplarini berishingiz mumkin. Bu Express'ning eng foydali, lekin ko'pchilik bilmaydigan imkoniyati:
import express, { Request, Response } from "express";
const app = express();
app.use(express.json());
// Kelayotgan body shakli β bizning shartnomamiz:
interface YangiFoydalanuvchi {
ism: string;
yosh: number;
}
// Request<Params, ResBody, ReqBody> β uchinchi generic body tipi:
app.post(
"/foydalanuvchi",
(req: Request<{}, {}, YangiFoydalanuvchi>, res: Response) => {
const { ism, yosh } = req.body; // ism: string, yosh: number β tiplangan!
res.status(201).json({ yaratildi: ism, yosh });
}
);
app.listen(3000);
Endi req.body.ism β string, req.body.yosh β number. req.body.familiya deb yozsangiz xato chiqadi, chunki YangiFoydalanuvchi'da bunday maydon yo'q.
π Muhim ogohlantirish: bu tip β faqat kompilyatsiya vaqtidagi va'da. Haqiqiy so'rov istalgan JSON yuborishi mumkin (yomon niyatli foydalanuvchi yosh: "yuz" jo'natishi mumkin). Tip "men shunday kutaman" deydi, lekin runtime'da haqiqatdan tekshirmaydi. Shuning uchun real loyihada body'ni zod kabi kutubxona bilan tekshirish kerak β bu haqda 21-bobda gaplashgandik. Tip β yordamchi, runtime tekshiruvning o'rnini bosmaydi.
π‘ Birinchi ikki generic ({}, {}) β Params va ResBody uchun. Bizga faqat body kerak bo'lgani uchun ularni bo'sh qoldirdik. Marshrut parametrini ham tiplash mumkin: Request<{ id: string }> β unda req.params.id tiplangan bo'ladi (URL parametrlari doim string bo'lishini eslang).
Environment'ni tiplash¶
process.env β environment o'zgaruvchilari (port, ma'lumotlar bazasi paroli, kalitlar). @types/node uni shunday tiplaydi: har bir qiymat string | undefined. Bu ataylab shunday β chunki o'zgaruvchi umuman o'rnatilmagan bo'lishi mumkin:
const port = process.env.PORT;
// port: string | undefined
// β Xato: 'string | undefined' β 'number'ni kutadigan joyga to'g'ridan-to'g'ri bermaysiz,
// va undefined bo'lishi mumkin
const n: number = port;
To'g'ri yondashuv β o'qing, mavjudligini tekshiring, kerak bo'lsa o'giring:
const portMatn = process.env.PORT ?? "3000"; // bo'lmasa default
const port = Number(portMatn); // string -> number
if (Number.isNaN(port)) {
throw new Error("PORT noto'g'ri qiymat");
}
π ?? "3000" (nullish coalescing) β process.env.PORT undefined bo'lsa, "3000"ni ishlatadi. Bu process.env'ni xavfsiz o'qishning standart qolipi: avval default ber, keyin kerakli tipga o'gir, keyin tekshir.
π‘ Real loyihada hamma environment o'qishni bitta config.ts fayliga yig'ish va u yerda bir marta tekshirish odat tusiga kiradi. Shunda qolgan kod tip-xavfsiz, tekshirilgan config.port (number) bilan ishlaydi β process.env'ni har joyda qaytadan tekshirish shart bo'lmaydi.
Yig'ib: kichik tiplangan API¶
Endi o'rgangan hamma narsani bitta kichik, tiplangan endpoint'da birlashtiramiz β javob shaklini ham interface bilan tasvirlaymiz:
import express, { Request, Response } from "express";
const app = express();
app.use(express.json());
// Javob shakli β shartnoma:
interface KitobJavobi {
id: number;
nom: string;
narx: number;
}
// "ma'lumotlar bazasi" o'rnida oddiy massiv:
const kitoblar: KitobJavobi[] = [
{ id: 1, nom: "O'tkan kunlar", narx: 50000 },
{ id: 2, nom: "Sariq devni minib", narx: 35000 },
];
// GET /kitoblar/:id β bitta kitobni qaytaradi
app.get("/kitoblar/:id", (req: Request<{ id: string }>, res: Response) => {
const id = Number(req.params.id);
const kitob = kitoblar.find((k) => k.id === id);
if (!kitob) {
res.status(404).json({ xato: "Topilmadi" });
return;
}
res.json(kitob); // KitobJavobi shaklida β tiplangan
});
app.listen(3000, () => console.log("Tayyor"));
Bu yerda hamma narsa tiplangan: req.params.id β string (shuning uchun Number(...) qilamiz), kitob β KitobJavobi | undefined (shuning uchun if (!kitob) narrowing kerak), va res.json(kitob) faqat KitobJavobi shaklini chiqaradi. Bitta zanjirning hamma bo'g'ini tip himoyasi ostida.
π tsx server.ts bilan ishga tushirganda bu kod kompilyatsiyasiz darrov ishlaydi. Production uchun: tsc β node dist/server.js.
π‘ Bu bob ataylab chuqur emas β React'ning o'zi (hooks, kontekst, ref) va Express'ning o'zi (middleware, router, error handling) alohida katta mavzular. Bu yerda asosiy g'oya: o'rgangan tiplaringiz β interface, union, generic, narrowing β real freymvorkda aynan shunday qo'llaniladi. Tip tushunchasi bir xil; faqat uni props, req, res kabi yangi joylarda ishlatasiz.
Xulosa¶
| Joy | Tiplash usuli |
|---|---|
| React props | interface Props { ... } + function K({ ... }: Props) |
| Holat | useState(0) (inference) yoki useState<T \| null>(null) |
| Event | inline'da avtomatik; alohida funksiyada React.ChangeEvent<...> |
| children | children: ReactNode |
| Komponent | function + props interface (React.FC shart emas) |
| Node API | @types/node o'rnating |
| Express req/res | @types/express, Request/Response, generic body |
| Environment | process.env.X β string \| undefined, default + tekshiruv |
| Ishga tushirish | dev: tsx, prod: tsc keyin node |
Asosiy xulosa: TypeScript freymvorkka qarshi emas β uning ichida ishlaydi va aynan o'sha yerda eng ko'p foyda beradi. Siz hech qanday "yangi til" o'rganmadingiz; faqat tanish tiplarni yangi joylarga qo'ydingiz.
22-bob mashqlari¶
Quyidagi mashqlar uchun React tomoni
.tsxfayl va@types/react, Node tomoni esa@types/node(va kerakli joyda@types/express) o'rnatilgan bo'lishi kerak. Yechimlarni o'zingiz yozing.
interface SalomProps { ism: string }tuzing va<h1>Salom, {ism}!</h1>qaytaradiganSalomkomponentini shu props bilan tiplang.Salom'ni<Salom ism="Aziz" />deb to'g'ri, keyin<Salom name="Aziz" />deb noto'g'ri ishlatib ko'ring β qaysi biri kompilyatsiyadan o'tmasligini tushuntiring.MahsulotKartasikomponentini yarating: props'danom: string,narx: numberva optionalchegirma?: number. Chegirma berilmasa 0 deb hisoblang.Tugmakomponentini yozing:rangpropi faqat"kok" | "qizil" | "yashil"union literal qiymatlarini qabul qilsin. Boshqa rang berilsa xato chiqishini tekshiring.useStatebilannumberturidagi hisoblagich yarating va TypeScript tipni o'zi (annotation'siz) aniqlaganini ko'rsating.useState<string>("")bilan boshqariladigan (controlled) input maydon yozing.interface Foydalanuvchi { id: number; ism: string }tuzing vauseState<Foydalanuvchi | null>(null)holatini yarating; render'danullholatini narrowing bilan to'g'ri ko'rsating.- 7-mashqdagi holatni
nullqilmasdan, faqatuseState(null)deb yozib ko'ring va keyin obyekt o'rnatmoqchi bo'lganda nima uchun xato chiqishini izohlang. onChangehandler'ini alohida funksiya sifatida yozing va parametriniReact.ChangeEvent<HTMLInputElement>bilan tiplang.- Xuddi shu handler'ni JSX ichida inline yozing va event tipini TypeScript o'zi aniqlaganini ko'rsating (qo'lda yozmasdan).
- Tugma uchun
onClickhandler'iniReact.MouseEvent<HTMLButtonElement>bilan tiplang va bosilganda konsolga yozsin. interface KartaProps { sarlavha: string; children: React.ReactNode }tuzing vaKartakomponentini yarating; uni ichiga<p>va<button>solib ishlating.- 12-mashqdagi
childrentipinistring'ga o'zgartiring va ichiga JSX qo'yganda nega xato chiqishini tushuntiring. - Bitta komponentni avval
React.FC<Props>bilan, keyinfunction+ props interface bilan yozing; ikki uslubni solishtiring va qaysi biri tavsiya etilishini ayting. @types/nodeo'rnatilgan loyihadaprocess.env.PORT'ni o'qing; uning tipistring | undefinedekanini ko'rsating va?? "3000"bilan default bering.- 15-mashqdagi
PORT'niNumber(...)bilannumber'ga o'giring vaNumber.isNaNbilan noto'g'ri qiymatni tekshiring. - Express'da
GET /salommarshrutinireq: Request,res: Responsebilan tiplang; ures.json({ xabar: "Salom" })qaytarsin. POST /foydalanuvchimarshrutini yarating:req'niRequest<{}, {}, { ism: string; yosh: number }>bilan tiplang vareq.body'danism,yosh'ni xavfsiz oling.GET /kitoblar/:idmarshrutini yozing:Request<{ id: string }>bilan params'ni tiplang,id'ni songa o'giring va massivdan toping; topilmasa404qaytaring.- Bitta mini loyiha tuzing: javob shaklini
interface KitobJavobibilan tasvirlang,GET /kitoblar(hammasi) vaGET /kitoblar/:id(bittasi) endpoint'larini yozing, hammasinitsxbilan ishga tushiring.