IlmHamroh
JavaScript Full-stack/17-qism. React asoslari24/45-dars26 daqiqa
Mundarija (36)

Amaliy: birinchi to'liq React mini-ilova — vazifalar ro'yxati va Vercel

Qisqacha: Bu darsda 17-qismning birinchi yarmi bitta ilovada birlashadi. Avval kichik vazifalar ro'yxatini noldan yozamiz: sof funksiyalar (o'zgarmas yangilash), state tuzilmasi (vazifalar, filtr, tahrirdagi id), uncontrolled + FormData forma, controlled tahrir, key va hosilaviy hisob. Keyin kursdagi vazifalar ilovasining butun interfeysi React'ga o'tadi va uni vanilla versiya bilan o'lchab solishtiramiz. Kod 14.3 KB dan 83.1 KB ga (gzip) o'sdi, lekin 1 000 vazifada bitta katakni belgilash 47–77 ms dan 19–20 ms ga tushdi. Oxirida ilovani o'z hisobingiz bilan Vercel'ga joylaysiz — vercel.json tayyor.

Bu darsda

  • To'liq CRUD ilova yozasiz: qo'shish, belgilash, tahrirlash, o'chirish, filtr, hisob.
  • Har qarorni oldingi darslar bilan bog'laysiz: qayerda state, qayerda props, qaysi forma controlled.
  • vazifalar da oxirgi vanilla bo'laklarni (tema tanlovi, izoh oroli) React'ga ko'chirasiz.
  • Vanilla va React'ni haqiqiy raqamlar bilan taqqoslaysiz: hajm, kod, tezlik, Lighthouse.
  • vercel.json ni o'qiysiz va ilovani Vercel'ga deploy qilasiz.

Oldin bilishingiz kerak: Kichik React loyihani tashkil qilish, Formalar I, Formalar II, State'ning saqlanishi, State strukturasi.

1. Nega bu kerak?

23 dars davomida React'ni bo'lakma-bo'lak o'rgandik: JSX, komponentlar, props, ro'yxat va key, hodisalar, useState, state tuzilmasi, formalar. Lekin haqiqiy ishda bular alohida kelmaydi: bitta ekranda forma, ro'yxat, filtr va hisob bir-biriga bog'langan. Qaysi state qayerda turadi, kim kimga nima beradi — bu savollarga faqat butun ilova yozganda duch kelasiz.

Shuning uchun bu dars ikki qismdan iborat. Birinchisida kichik ilovani noldan yozamiz — hamma qadam ko'z oldingizda. Ikkinchisida kursdagi vazifalar ning React'ga ko'chishini yakunlaymiz. Ilova 9-qismdan beri vanilla edi va 17/#02 dan boshlab asta-sekin React'ga o'tdi (Birinchi React loyiha darsidagi orol). Oxirida esa savolga halol javob beramiz: React bizga nima berdi va nima oldi?

Hayotiy o'xshatish: oshpazlik kursida har darsda bitta usul — to'g'rash, qovurish, dimlash. Bugun — birinchi to'liq osh, hamma usul bitta qozonda.

2. Reja: komponentlar va state

2.1 Talablar

«Bahor» oshxonasining ichki vazifalar ro'yxati:

  • yangi vazifa qo'shish (bo'sh yoki takror matn — xato xabari);
  • bajarildi deb belgilash;
  • matnni tahrirlash (Enter — saqlash, Esc — bekor);
  • o'chirish;
  • filtr: hammasi / faol / bajarilgan;
  • pastda: "N ta qoldi, M ta bajarildi".

2.2 State — minimal

State strukturasi darsidagi savol: nima saqlanadi, nima hisoblanadi?

Ma'lumot State yoki hosilaviy qiymat Nega
vazifalar ro'yxati state foydalanuvchi o'zgartiradi
tanlangan filtr state foydalanuvchi tanlaydi
qaysi vazifa tahrirda state — id obyekt emas, id (takrorlamang)
ko'rinadigan vazifalar hosilaviy qiymat tasks + filter dan
"N ta qoldi" hosilaviy qiymat tasks dan
tahrirdagi matn EditRow ning state'i faqat u ishlatadi (colocation)
forma xatosi NewTaskForm ning state'i faqat u ko'rsatadi

Uchta state App da, ikkitasi — o'z komponentida. Hammasi bitta joyda emas: State'ni ko'tarish darsidagi "keragidan baland emas" qoidasi.

2.3 Komponentlar daraxti

flowchart TD
  A["App: tasks, filter, editId"] --> F["NewTaskForm: error"]
  A --> B["FilterBar"]
  A --> L["ul — shown.map"]
  L --> V["ViewRow"]
  L --> E["EditRow: text"]
  A --> S["Stats"]

Ma'lumot pastga (props), hodisalar yuqoriga (onAdd, onChange, onToggle, onSave). ViewRow va EditRow — bir o'rinda ikki tur: tahrir boshlanganda EditRow yangidan yaratiladi va vazifaning joriy matnini oladi (State'ning saqlanishi).

3. Model: sof funksiyalar

Avval React'siz qism — ma'lumot va uni o'zgartiradigan funksiyalar. Ular immutable yangilash qoidasiga bo'ysunadi: hech biri massivni o'zgartirmaydi, hammasi yangisini qaytaradi.

tsx
// model — ma'lumot va sof funksiyalar (React'siz)
export type Task = { id: number; text: string; done: boolean };
export type Filter = "hammasi" | "faol" | "bajarilgan";
type Tasks = readonly Task[]; // faqat o'qish uchun massiv

export const FILTERS: readonly Filter[] = [
  "hammasi",
  "faol",
  "bajarilgan",
];

function nextId(tasks: Tasks): number {
  return Math.max(0, ...tasks.map((t) => t.id)) + 1;
}

export function addTask(tasks: Tasks, text: string): Task[] {
  return [...tasks, { id: nextId(tasks), text, done: false }];
}

export function toggleTask(tasks: Tasks, id: number): Task[] {
  return tasks.map((t) =>
    t.id === id ? { ...t, done: !t.done } : t,
  );
}

export function renameTask(
  tasks: Tasks,
  id: number,
  text: string,
): Task[] {
  return tasks.map((t) => (t.id === id ? { ...t, text } : t));
}

export function removeTask(tasks: Tasks, id: number): Task[] {
  return tasks.filter((t) => t.id !== id);
}

export function visibleTasks(tasks: Tasks, filter: Filter): Tasks {
  if (filter === "faol") return tasks.filter((t) => !t.done);
  if (filter === "bajarilgan") return tasks.filter((t) => t.done);
  return tasks;
}

export function validate(
  tasks: Tasks,
  text: string,
  exceptId: number | null = null,
): string | null {
  if (text === "") return "Vazifa matnini yozing";
  if (text.length > 100) return "100 belgidan oshmasin";
  const lower = text.toLowerCase();
  const same = tasks.some(
    (t) => t.id !== exceptId && t.text.toLowerCase() === lower,
  );
  return same ? "Bunday vazifa bor" : null;
}

const start: Task[] = [{ id: 1, text: "Non olish", done: false }];
const a = addTask(start, "Sut olish");
const b = toggleTask(a, 1);
console.log(b.map((t) => `${t.id}:${t.text}:${t.done}`).join(" | "));
console.log(start[0].done, visibleTasks(b, "faol").length);
console.log(validate(b, "non olish"), validate(b, "Choy"));

Konsolda:

text
1:Non olish:true | 2:Sut olish:false
false 1
Bunday vazifa bor null

Oxirgi uch qator — kichik sinov. Birinchi vazifaning asl nusxasi (start[0].done) hali false: toggleTask asl massivga tegmadi. Tekshiruv funksiyasi takror matnni katta-kichik harfga qaramay topdi ("non olish"). Uchinchi parametr exceptId — tahrirda vazifaning o'zi bilan solishtirmaslik uchun: "Non olish" ni "Non olish" ga saqlash — takror emas.

Tasks = readonly Task[] — TypeScript da "bu massivni o'zgartirma": tasks.push(…) yozsangiz, tsc to'xtatadi. nextId — eng katta id + 1. Bu oddiy yo'l, lekin eng kattasi o'chirilsa, uning id'si qayta berilishi mumkin. vazifalar da ham shunday, va bu TEXNIK-QARZ ga yozilgan (server id'ni o'zi beradi).

Maslahat: Sof funksiyalarni React'dan ajratib yozish — Kichik React loyihani tashkil qilish dagi lib/ g'oyasi. Haqiqiy loyihada bu qism src/lib/tasks.ts bo'lardi va node --test bilan sinalardi.

4. Komponentlar

Endi React qismi. Quyidagi blok oldingisining davomi — model funksiyalari shu yerda ishlatiladi.

tsx
import { useState } from "react";
import type { FormEvent, KeyboardEvent } from "react";

type NewTaskFormProps = { onAdd: (text: string) => string | null };

// Uncontrolled + FormData: qiymat faqat yuborilganda kerak
function NewTaskForm({ onAdd }: NewTaskFormProps) {
  const [error, setError] = useState<string | null>(null);

  function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const form = e.currentTarget;
    const text = String(new FormData(form).get("text") ?? "").trim();
    const problem = onAdd(text);
    setError(problem);
    if (problem === null) form.reset(); // defaultValue yo'q
  }

  return (
    <form onSubmit={handleSubmit} noValidate>
      <label>
        Vazifa <input name="text" aria-invalid={error !== null} />
      </label>{" "}
      <button>Qo'sh</button>
      <p role="status">{error}</p>
    </form>
  );
}

type FilterBarProps = {
  filter: Filter;
  onChange: (f: Filter) => void;
};

const FILTER_NAMES: Record<Filter, string> = {
  hammasi: "Hammasi",
  faol: "Faol",
  bajarilgan: "Bajarilgan",
};

// Controlled: tanlov otada, tugmalar faqat xabar beradi
function FilterBar({ filter, onChange }: FilterBarProps) {
  return (
    <p role="group" aria-label="Filtr">
      {FILTERS.map((f) => (
        <button
          key={f}
          aria-pressed={f === filter}
          onClick={() => onChange(f)}
        >
          {FILTER_NAMES[f]}
        </button>
      ))}
    </p>
  );
}

type EditRowProps = {
  task: Task;
  onSave: (text: string) => void;
  onCancel: () => void;
};

// Controlled tahrir: matn state'da, boshlang'ichi — vazifa matni
function EditRow({ task, onSave, onCancel }: EditRowProps) {
  const [text, setText] = useState(task.text);

  function handleKey(e: KeyboardEvent<HTMLInputElement>) {
    if (e.key === "Enter") onSave(text);
    if (e.key === "Escape") onCancel();
  }

  return (
    <li>
      <input
        aria-label="Matn"
        value={text}
        onChange={(e) => setText(e.target.value)}
        onKeyDown={handleKey}
      />{" "}
      <button onClick={() => onSave(text)}>Saqla</button>
      <button onClick={onCancel}>Bekor</button>
    </li>
  );
}

type ViewRowProps = {
  task: Task;
  onToggle: () => void;
  onEdit: () => void;
  onRemove: () => void;
};

function ViewRow({ task, onToggle, onEdit, onRemove }: ViewRowProps) {
  return (
    <li>
      <label>
        <input
          type="checkbox"
          checked={task.done}
          onChange={onToggle}
        />
        {task.done ? <s>{task.text}</s> : task.text}
      </label>{" "}
      <button onClick={onEdit} aria-label={`Tahrir ${task.text}`}>
        ✎
      </button>
      <button onClick={onRemove} aria-label={`O'chir ${task.text}`}>
        ✕
      </button>
    </li>
  );
}

function Stats({ tasks }: { tasks: Tasks }) {
  const done = tasks.filter((t) => t.done).length; // hosilaviy
  return (
    <p aria-live="polite">
      {tasks.length - done} ta qoldi, {done} ta bajarildi
    </p>
  );
}

Har komponentda bitta darsning izi:

Komponent Nima ishlatadi Dars
NewTaskForm uncontrolled, FormData, noValidate, reset() Formalar II
FilterBar controlled komponent, aria-pressed, Record State'ni ko'tarish
EditRow controlled input, useState(task.text), Enter/Esc Formalar I
ViewRow controlled checkbox, aria-label, callback props Hodisalar
Stats hosilaviy qiymat State strukturasi

form.reset() bu yerda xavfsiz: maydonda defaultValue yo'q, demak "boshlang'ich qiymat" — bo'sh satr. Formalar II dagi tuzoq faqat defaultValue bo'lganda. Belgi tugmalari (, ) faqat ko'rinish uchun. Malikaning ekran o'quvchisi aria-label ni o'qiydi: "Tahrir Non olish". Har qatorda tugma nomi boshqa — "qaysi vazifaning tugmasi?" degan savol yo'q.

5. App — hammasi birga

tsx
const START: Task[] = [
  { id: 1, text: "Non olish", done: false },
  { id: 2, text: "Onamga qo'ng'iroq", done: false },
];

export default function App() {
  const [tasks, setTasks] = useState<Task[]>(START);
  const [filter, setFilter] = useState<Filter>("hammasi");
  const [editId, setEditId] = useState<number | null>(null);
  const shown = visibleTasks(tasks, filter); // hosilaviy

  function handleAdd(text: string): string | null {
    const problem = validate(tasks, text);
    if (problem === null) setTasks(addTask(tasks, text));
    return problem;
  }

  function handleSave(id: number, text: string) {
    const clean = text.trim();
    if (validate(tasks, clean, id) !== null) return;
    setTasks(renameTask(tasks, id, clean));
    setEditId(null);
  }

  return (
    <main>
      <h1>Vazifalar</h1>
      <NewTaskForm onAdd={handleAdd} />
      <FilterBar filter={filter} onChange={setFilter} />
      <ul>
        {shown.map((t) =>
          t.id === editId ? (
            <EditRow
              key={t.id}
              task={t}
              onSave={(text) => handleSave(t.id, text)}
              onCancel={() => setEditId(null)}
            />
          ) : (
            <ViewRow
              key={t.id}
              task={t}
              onToggle={() => setTasks(toggleTask(tasks, t.id))}
              onEdit={() => setEditId(t.id)}
              onRemove={() => setTasks(removeTask(tasks, t.id))}
            />
          ),
        )}
      </ul>
      {shown.length === 0 && <p>Bu yerda vazifa yo'q.</p>}
      <Stats tasks={tasks} />
    </main>
  );
}

Ekranda:

text
Vazifalar
Vazifa Qo'sh
HammasiFaolBajarilgan
Non olish ✎✕
Onamga qo'ng'iroq ✎✕
Sut olish ✎✕
2 ta qoldi, 1 ta bajarildi

«Ishga tushir» ni bosib, ilovani o'zingiz sinab ko'ring. Pastdagi natija — "Sut olish" qo'shilib, "Non olish" belgilangandan keyingi holat (ekran natijasida <s> ichidagi matn oddiy matn bo'lib ko'rinadi). Qadamma-qadam:

Qadam tasks filter, editId Ekranda
boshida 2 ta, hammasi faol hammasi, null 2 qator, "2 ta qoldi, 0 ta bajarildi"
"Sut olish" qo'shildi 3 ta hammasi, null 3 qator, "3 ta qoldi"
"Non olish" belgilandi 1-si done: true hammasi, null "2 ta qoldi, 1 ta bajarildi"

5.1 Boshqa ssenariylar

Xuddi shu ilova, boshqa harakatlar bilan. Tahrir: "Non olish" → "Lavash".

tsx
// (yuqoridagi ilova — boshqa ssenariy)

Ekranda:

text
Vazifalar
Vazifa Qo'sh
HammasiFaolBajarilgan
Lavash ✎✕
Onamga qo'ng'iroq ✎✕
2 ta qoldi, 0 ta bajarildi

Takror vazifa va filtr:

tsx
// (yuqoridagi ilova — boshqa ssenariy)

Ekranda:

text
Vazifalar
Vazifa Qo'sh
Bunday vazifa bor
HammasiFaolBajarilgan
Non olish ✎✕
Onamga qo'ng'iroq ✎✕
2 ta qoldi, 0 ta bajarildi

Xato xabari chiqdi va vazifa qo'shilmadi. NewTaskForm reset() qilmadi — "non olish" matni maydonda qoldi, tuzatish mumkin.

Tekshirib ko'ring: "Faol" filtrida "Non olish" ni belgilasak, u ro'yxatdan yo'qoladi. Agar o'sha paytda boshqa vazifa tahrirda bo'lsa, uning yozilayotgan matni saqlanadimi?

Javob

Ha. EditRow ning key i — vazifa id'si, u o'zgarmadi. Boshqa qator ro'yxatdan chiqqani uchun tahrirdagi qator o'rni siljiydi, lekin React uni key bo'yicha taniydi va state'ini (text) saqlaydi. key={index} bo'lganda esa matn yo'qolardi (State'ning saqlanishi, 3-mashq).

6. Vazifalar qadami: butun UI React'da

6.1 Nima qoldi

Kichik React loyihani tashkil qilish darsidan keyingi holatda vazifalar ning deyarli hamma qismi React'da. index.html da faqat ikki bo'lak qolgan:

  • tema tanlovi (<fieldset id="tema-tanlov" hidden> — tizim/yorug'/qorong'i radio tugmalari), uni hali vanilla temaniUla() boshqaradi;
  • izoh oroli (<div id="izoh">) — Birinchi React loyiha darsidagi birinchi komponent, alohida createRoot bilan main.tsx dan.

Yangi branch: feat/react-ui.

6.2 TemaTanlov.tsx

Kanondan (kod aynan, izoh qatorlari telefon uchun qayta bo'lingan):

tsx
// TemaTanlov.tsx — tizim / yorug' / qorong'i (13/#05). 17/#24 da
// React'ga ko'chdi: qaysi tugma belgilangani — state
// (controlled radio)
import { Fragment, useState } from "react";
import type { ChangeEvent } from "react";
import {
  TEMALAR,
  saqlanganTanlov,
  tanlovniOl,
  tanlovniSaqla,
} from "../tema-tanlash.ts";
import type { Tema } from "../tema-tanlash.ts";

const NOMLAR: Readonly<Record<Tema, string>> = Object.freeze({
  tizim: "Tizim",
  yorug: "Yorug'",
  qorongi: "Qorong'i",
});

export function TemaTanlov() {
  // Funksiya — boshlang'ich qiymatni faqat BIRINCHI renderda
  // hisoblaydi (localStorage har renderda o'qilmaydi)
  const [tanlov, setTanlov] = useState(saqlanganTanlov);

  function tanlandi(event: ChangeEvent<HTMLInputElement>) {
    const yangi = tanlovniOl(event.target.value);
    setTanlov(yangi);
    tanlovniSaqla(yangi); // xotira, <html data-tema>, panel rangi
  }

  // v6 da fieldset index.html da hidden edi va JS uni ochardi
  // (JS ishlamasa — tugmalar ko'rinmasin). Endi uni React chizadi:
  // JS yo'q bo'lsa, umuman yo'q
  return (
    <fieldset id="tema-tanlov">
      <legend>Tema</legend>
      {TEMALAR.map((tema) => (
        <Fragment key={tema}>
          <input
            type="radio"
            name="tema"
            id={`tema-${tema}`}
            value={tema}
            checked={tanlov === tema}
            onChange={tanlandi}
          />
          <label htmlFor={`tema-${tema}`}>{NOMLAR[tema]}</label>
        </Fragment>
      ))}
    </fieldset>
  );
}

Bu yerda yangi narsa bitta — useState(saqlanganTanlov). Qavslarsiz! saqlanganTanlov() chaqirilmagan, funksiyaning o'zi berilgan. React uni faqat birinchi renderda chaqiradi va natijasini boshlang'ich qiymat qiladi — bu lazy initialization ("dangasa boshlash"). useState(saqlanganTanlov()) yozilsa ham ishlaydi, lekin localStorage har renderda o'qiladi va natijasi tashlab yuboriladi. Qolgani tanish: controlled radio (Formalar I), <Fragment key> (JSX II) — har tema uchun input va label juftligi.

tema-tanlash.ts dagi temaniUla() ikkiga bo'lindi: radio tugmalarga tegadigan qismi — endi komponentda. <html data-tema> va telefon panelining rangi (theme-color) esa temaniQolla() da qoldi — bu sahifa sozlamasi, React daraxtidan tashqarida. asosiy.ts ishga tushganda:

ts
// Panel rangi (theme-color) — saqlangan tanlov bo'yicha; <html
// data-tema> ni tema.js allaqachon qo'ygan
temaniQolla(saqlanganTanlov());
tizimTemasiniKuzat();
render();

6.3 App va index.html

App.tsx ga ikki qator:

diff
       <Import onImport={amallar.onImport} />
       <OchirishDialog matn={holat.ochirilayotgan} />
+      <TemaTanlov />
+      <Izoh />
     </>

index.html dagi <main> endi shunday:

diff
   <main>
     <h1>Vazifalar</h1>
-    <!-- React ildizi (17/#16): aloqa, forma, qidiruv, filtr, ro'yxat,
-         hisob, e'lon, eksport/import (#19), dialog — src/App.tsx -->
+    <!-- React ildizi: butun ilova — src/App.tsx (17/#16, #24) -->
     <div id="ilova"></div>
-    <fieldset id="tema-tanlov" hidden>
…
-    <!-- React oroli (17/#02): ichini src/main.tsx chizadi -->
-    <div id="izoh"></div>
   </main>

(… — radio tugmalarining 7 qatori.) Yana ikki o'zgarish bor. <head> dagi main.tsx skripti olib tashlandi va fayl o'chirildi — izoh endi alohida orol emas, App ichida. Sahifa tavsifi (<meta name="description">) ham yangilandi: "React'da yozilgan vazifalar ro'yxati." main.tsx keyinroq butun ilovaning yagona kirish nuqtasi bo'lib qaytadi, state App ga ko'chganda (useEffect III).

<body> ichida endi 7 qator HTML. Sahifadagi har element — sarlavhadan tashqari — React chizadi.

6.4 Tekshiruv va commit

bash
npm run build && npm test && npm run lint && npm run tip

Kanon natijalari: node:test — 149/149, Vitest — 22/22, brauzer tekshiruvi — 159/159 (tema tanlovi, oflayn rejim, CSP va boshqalar o'zgarishsiz). Diff: 7 fayl, +101 −72.

text
feat: to'liq React UI — tema tanlovi va izoh App ichida

Body: "TemaTanlov (controlled radio, useState lazy init); index.html <main> — <h1> + <div id="ilova">. main.tsx oroli olib tashlandi. Vanilla DOM kodi qolmadi." PR'ni birlashtiring.

7. Vanilla va React: o'lchangan taqqoslash

Endi bir xil ilovaning ikki versiyasi bor: v6 (16-qism, vanilla + TypeScript) va hozirgi holat (React). Ikkalasi bir xil 159 brauzer tekshiruvidan o'tadi — xulqi bir xil. Farqni o'lchadik (Chrome 154 headless, Intel i5-12500H noutbuk).

7.1 Hajm

Brauzer yuklaydigan JS va CSS (build natijasi, bayt):

v6 (vanilla) React (shu holat)
xom 38 066 261 396
gzip -9 14 279 83 107
brotli 13 033 72 012

Taxminan 6–7 barobar. Qayerdan? npm run analiz (Vite V: bundle tahlili) javob beradi — minifikatsiyadan oldingi hajm ulushi:

Modul Ulush
react-dom-client.production.js 83.8% (517 KiB)
react.production.js 2.5%
web-vitals 1.8%
scheduler 1.4%
bizning asosiy.ts 1.3%

O'sishning deyarli hammasi — react-dom. Bizning kodimiz React'ga o'tganda kichraymadi ham, kattalashmadi ham: farqni kutubxona berdi.

Bu raqamlar — shu darsdagi holat. Qism oxirida (effektlar, Context va hooklar qo'shilgach) gzip hajm 83.2 KB bo'ladi — React nima va nega darsidagi raqam shu yakuniy holatniki. Farq 0.1 KB, ya'ni deyarli hamma narsa shu darsda allaqachon to'langan.

7.2 Kod

v6 React
UI kodi render.ts 399 + asosiy.ts 746 App.tsx 128 + komponentlar 867 + render.tsx 168 + asosiy.ts 541
index.html <body> 92 qator 7 qator
jami 1 237 1 711
src/ fayllari 22 43

Kutilmagan natija: kod ko'paydi (izohlar bilan sanalgan). Komponentlarga bo'lish fayllar va props turlarini qo'shdi. React kodni qisqartirish uchun emas — u tartib beradi: har bo'lak alohida, "ekran = state dan funksiya", DOM'ni qo'lda yangilash yo'q.

7.3 Tezlik: 1 000 vazifa

Ro'yxatga 1 000 ta vazifa yuklandi va ikki ish o'lchandi: qidiruv (1 000 qatordan 112 qatorga va qaytib) va bitta katakni belgilash. Har ilova 3 marta navbat bilan, har safar 15 o'lchov, natija — mediana:

O'lchov v6 React
qidiruv, faqat JS 11.3 / 12.5 / 12.6 ms 16.4 / 16.8 / 19.0 ms
qidiruv, JS + joylashtirish 33.2 / 34.6 / 43.9 ms 36.3 / 36.9 / 38.2 ms
bitta katak (+ joylashtirish) 47.2 / 51.5 / 77.3 ms 18.8 / 19.3 / 20.0 ms
1 000 vazifada bitta katakni belgilash (mediana)
  • v6, 1-o'lchov77,3 ms
  • v6, 2-o'lchov51,5 ms
  • v6, 3-o'lchov47,2 ms
  • React, 1-o'lchov19,3 ms
  • React, 2-o'lchov18,8 ms
  • React, 3-o'lchov20 ms

Manba: IlmHamroh o'lchovi, 2026-10-12: Chrome 154 headless, i5-12500H, har biri 15 o'lchov medianasi

Ikki xil natija, ikki xil sabab:

  • Bitta katak — React 2.4–4 marta tez. v6 da har o'zgarishda butun ro'yxat qayta yasalardi (replaceChildren — 1 000 ta yangi <li>). React esa yangi va eski elementlarni key bo'yicha solishtiradi va faqat o'zgargan bitta qatorni yangilaydi.
  • Qidiruv — React biroz sekinroq (JS qismi 4–6 ms ko'proq). Ko'p qator birdaniga o'zgarganda React'ning solishtirish ishi qo'shimcha xarajat bo'ladi. Joylashtirish (layout) bilan birga esa farq deyarli yo'q.

Ikkala holatda ham vaqt 50 ms dan past (React'da) — foydalanuvchi uchun "darhol". Kompilyator yoki memo ishlatilmagan (React Compiler — 18-qism).

7.4 Lighthouse

Mobil rejim, Lighthouse 13.5.0, ikki o'lchov:

Performance Accessibility, Best Practices, SEO LCP
v6 100 100 / 100 / 100 —
React, 1-o'lchov 97 100 / 100 / 100 2.6 s
React, 2-o'lchov 98 100 / 100 / 100 2.1 s

React versiyasida LCP 2.1–2.6 s, TBT 50–110 ms, CLS 0. Performance ballining 2–3 ga tushishi — 69 KB qo'shimcha JS'ni yuklash va bajarish narxi. Accessibility va boshqalar o'zgarmadi: biz HTML'ni, aria-* atributlarini va fokusni saqlab qoldik.

7.5 Xulosa: nima berdi, nima oldi

React berdi React oldi
deklarativ UI: "state shunday bo'lsa — ekran shunday" +69 KB gzip JS
bitta qator o'zgarganda tez yangilanish ko'p qator o'zgarganda biroz sekinroq
fokus va yozilgan matn o'z-o'zidan saqlanadi (key) yangi tushunchalar: render, state, hooklar
komponentlarni alohida sinash (Vitest, 22 test) ko'proq fayl va qator

Bu narx vazifalar ning TEXNIK-QARZ ida ochiq yozilgan va ongli ravishda qabul qilingan. Kichik statik sahifa uchun React ortiqcha bo'lishi mumkin. Holat ko'p, interfeys murakkab va jamoa katta bo'lsa — tartib narxga arziydi.

8. Vercel'ga deploy

8.1 Vercel nima

Vercel — frontend ilovalarni joylash (hosting) xizmati. GitHub repozitoriyangizni ulaysiz — har push dan keyin Vercel o'zi npm ci va npm run build qiladi, natijani (dist/) dunyo bo'ylab tarqatilgan serverlarga (CDN) joylaydi va manzil beradi: loyiha-nomi.vercel.app. Shaxsiy, notijorat loyihalar uchun bepul (Hobby rejasi). Hozirgacha vazifalar GitHub Pages'da edi (GitHub Pages va Actions). Vercel bunga ikki qulaylik qo'shadi: har PR uchun alohida "preview" manzil va HTTP sarlavhalarini sozlash.

Bu qadamni o'zingiz bajarasiz — o'z GitHub va Vercel hisobingiz bilan. Biz sizning hisobingizga kira olmaymiz, shuning uchun bu bo'limdagi interfeys tavsifi — Vercel'ning rasmiy hujjatidan (vercel.com/docs, 2026-10 holati).

8.2 vercel.json — kanondan

Repo ildizida tayyor fayl:

json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "framework": "vite",
  "installCommand": "npm ci",
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }],
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy",
          "value": "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self' https://ilmhamroh.uz; manifest-src 'self'; worker-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; require-trusted-types-for 'script'; trusted-types vazifalar"
        },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        }
      ]
    },
    {
      "source": "/assets/((?!js/|ikonlar/).*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    },
    {
      "source": "/sw.js",
      "headers": [{ "key": "Cache-Control", "value": "no-cache" }]
    }
  ]
}

Kalitma-kalit:

Kalit Nima qiladi Yozilmasa (sukut bo'yicha)
$schema muharrirga avtomatik to'ldirish va tekshiruv beradi faqat muharrir uchun — deploy'ga ta'siri yo'q
framework, installCommand, buildCommand, outputDirectory Vercel nimani qanday build qilishi: npm ci (lock-fayl bo'yicha aniq), npm run build, natija — dist/ Vercel loyihadan o'zi aniqlaydi (Vite preset); biz aniq yozdik — taxmin qilmasin
rewrites har qanday manzil index.html ni qaytaradi — SPA uchun; mavjud fayl bo'lsa, fayl ustun yo'q — noma'lum manzil 404
CSP <meta> dagi bilan bir xil + frame-ancestors 'none' (sahifani iframe ga qo'yib bo'lmaydi — buni faqat sarlavha qila oladi) sarlavha yo'q, faqat <meta> dagi CSP
nosniff, Referrer-Policy HTTP sarlavhalari yo'q
/assets/… hash'li fayllar — bir yil, immutable; assets/js/ va assets/ikonlar/ (hash'siz, Rasm va assetlar) — bu qoidaga kirmaydi Vercel'ning o'z kesh sarlavhasi
/sw.js Service Worker har safar tekshirilsin — yangi versiya kech qolmasin Vercel'ning o'z kesh sarlavhasi

rewrites haqida: vazifalar da hozircha bitta sahifa, lekin filtr URL'da (?filtr=faol). Kelajakda marshrutlar (/vazifa/5) paydo bo'lsa, foydalanuvchi bu manzilni to'g'ridan-to'g'ri ochganda 404 emas, ilova ochiladi (React Router — 19-qism).

Fayl lokal sinalgan: sarlavhalar statik serverda qo'yildi va 159 brauzer tekshiruvi shu sarlavhalar ostida ham o'tdi (159/159). Quyidagi chiqishda har qatorda uch narsa bor: manzil va status, Cache-Control qiymati, CSP sarlavhasi bormi. O'rtadagi ustunga qarang:

text
/ 200 | no-store | true
/assets/js/tema.js 200 | no-store | true
/assets/ikonlar/ikon.svg 200 | no-store | true
/sw.js 200 | no-cache | true
/assets/index-BzjdvVca.js 200 | public, max-age=31536000, immutable | true

no-store — lokal serverning sukut qiymati (Vercel'da u o'zining sukut sarlavhasini qo'yadi). Muhimi: hash'li fayl — immutable, sw.js — no-cache, hash'siz tema.js va ikonka — uzoq keshlanmaydi.

8.3 Qadamlar (Vercel hujjatidan)

  1. vercel.com da GitHub hisobi bilan ro'yxatdan o'ting ("Continue with GitHub").
  2. Add New… → Project ni bosing va vazifalar repozitoriyasini tanlang ("Import"). Vercel'ga faqat shu repoga ruxsat berish mumkin — hamma repolaringizga emas.
  3. Sozlamalar sahifasida Framework Preset — "Vite" (avtomatik aniqlanadi). Build va output sozlamalari vercel.json dan olinadi — qo'lda o'zgartirmang.
  4. Environment Variables — vazifalar da kerak emas: VITE_API_URL .env.production da va u sir emas (brauzerga baribir chiqadi, Vite III: env). Sirni bu yerga ham, VITE_ o'zgaruvchiga ham qo'ymang.
  5. Deploy. Bir-ikki daqiqadan keyin manzil chiqadi: https://<loyiha-nomi>.vercel.app.

Keyingi har git push (asosiy branch'ga) — yangi production deploy. Har PR uchun Vercel alohida preview manzil beradi va PR'ga izoh qilib yozadi — o'zgarishni birlashtirishdan oldin ko'rish uchun.

Terminaldan ham mumkin: Vercel CLI (npx vercel, 2026-10 holatida versiyasi 63.1.2). Lekin u avval vercel login talab qiladi, shuning uchun biz uning chiqishini bu yerda ko'rsatmaymiz. Asosiy yo'l — yuqoridagi GitHub integratsiyasi.

8.4 Deploy'dan keyin tekshiring

  • Sahifa ochiladimi, vazifa qo'shiladimi (server — «Bahor» mashq API, connect-src CSP'da ruxsat etilgan).
  • DevTools → Network → hujjat so'rovi → Response Headers: content-security-policy da frame-ancestors 'none' bormi?
  • assets/index-….js sarlavhasida cache-control: public, max-age=31536000, immutable.
  • Internetni o'chirib, sahifani yangilang — Service Worker keshdan ochishi kerak (PWA).
  • Lighthouse (mobil) — taxminan 97–99 / 100 / 100 / 100.

GitHub Pages workflow'i repoda qoladi — kanon hozircha ikkalasini saqlaydi. Bitta ilovani ikki manzilda doimiy ushlash noqulay: README'dagi "Deploy" bo'limida o'tish tartibi bor (Vercel ishlayotgani tasdiqlangach, Pages workflow'i alohida commit bilan o'chiriladi). Vercel bilan chuqurroq ishlash — domen, muhitlar, cheklovlar — Deploy I: Vercel darsida.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
useState(oqi()) har renderda localStorage o'qiydi funksiya chaqirilgan useState(oqi) — lazy init
tahrirda boshqa qator o'chsa, matn yo'qoladi key={index} key={t.id}
"N ta qoldi" eskirib qoladi hisob alohida state hosilaviy qiymat
forma reset() dan keyin eski matn defaultValue bor faqat defaultValue siz reset()
Vercel'da /vazifa/5 — 404 SPA rewrite yo'q rewrites → /index.html
Vercel build yiqildi: npm ci lock-fayl xatosi package-lock.json commit qilinmagan yoki eskirgan lokal npm install, lock-faylni commit qiling
yangi versiya chiqmayapti sw.js keshlangan sw.js — no-cache

10. Mashqlar

Mashqlar kurs/mashqlar/17/24-mini-ilova/ da. 1–3-mashqlar — «Model», «Komponentlar» va «App» bo'limlaridagi ilovani o'zgartirish. O'z Vite loyihangizga ko'chiring yoki natija oynasida «Tahrirlab ko'rish» bilan ishlang.

1-mashq (oson): Raqamlarni o'qing

«Vanilla va React» bo'limidagi jadvallardan:

  • react-dom-client bundle'ning necha foizi:
  • 1 000 vazifada bitta katakni belgilash React'da taxminan necha ms (butun songa yaxlitlang, 2-o'lchov):
  • index.html <body> React versiyasida necha qator:
Yechim

83.8% — react-dom-client.production.js. 18.8 ms → 19. 7 qator: <main>, <h1>, izoh, <div id="ilova"> va yopuvchi teglar.

2-mashq (o'rta): "Bajarilganlarni tozalash"

Ilovaga Stats yoniga «Bajarilganlarni tozalash» tugmasini qo'shing. U faqat bajarilgan vazifa bo'lsa ko'rinsin (shartli render — hosilaviy), bosilganda bajarilganlar o'chsin. Sof funksiya — clearDone(tasks). Ishora: «Model» bo'limidagi removeTask ga o'xshash filter.

Yechim

Model'ga:

ts
export function clearDone(tasks: readonly Task[]): Task[] {
  return tasks.filter((t) => !t.done);
}

App da, <Stats> dan keyin:

tsx
{tasks.some((t) => t.done) && (
  <button onClick={() => setTasks(clearDone(tasks))}>
    Bajarilganlarni tozalash
  </button>
)}

To'liq yechim (yuqoridagi ilova + o'zgarish) mashq papkasida. Sinov: "Non olish" ni belgilang — tugma paydo bo'ladi; bosing — "Onamga qo'ng'iroq" qoladi, hisob "1 ta qoldi, 0 ta bajarildi", tugma yo'qoladi. tasks.some(…) — boolean, shuning uchun 0 && tuzog'i yo'q (JSX II).

3-mashq (qiyin): Muhim vazifalar

Vazifaga important: boolean maydonini qo'shing (TypeScript turi ham). Talablar:

  • NewTaskForm da «Muhim» checkbox (uncontrolled, FormData da get("important") === "on");
  • ViewRow da muhim vazifa oldida ★ (ekran o'quvchi uchun <span aria-label="muhim">★</span>);
  • ro'yxat tartibi: avval muhimlar, keyin qolganlari — hosilaviy, toSorted bilan, state'dagi tartib o'zgarmasin;
  • Stats ga "(K tasi muhim)" qo'shilsin, faqat faol muhimlar sanalsin.

Ishora: onAdd imzosi o'zgaradi — (text: string, important: boolean) => string | null; toSorted — immutable yangilash darsidan; boolean'ni songa aylantirib solishtirish: Number(b.important) - Number(a.important).

Yechim

Asosiy o'zgarishlar:

tsx
export type Task = {
  id: number;
  text: string;
  done: boolean;
  important: boolean;
};

export function sortByImportance(tasks: readonly Task[]): Task[] {
  // barqaror saralash: teng muhimlikda asl tartib saqlanadi
  return tasks.toSorted(
    (a, b) => Number(b.important) - Number(a.important),
  );
}

// NewTaskForm ichida
const data = new FormData(form);
const important = data.get("important") === "on";
const problem = onAdd(text, important);

// App ichida
const shown = sortByImportance(visibleTasks(tasks, filter));

// Stats ichida
const urgent = tasks.filter((t) => t.important && !t.done).length;

To'liq tekshirilgan yechim mashq papkasida. toSorted yangi massiv qaytaradi — tasks state'dagi tartib (qo'shilish tartibi) o'zgarmaydi, saralash faqat ko'rinishda. Array.prototype.sort (va toSorted) ES2019 dan beri barqaror — teng elementlar tartibini saqlaydi.

4-mashq: Vazifalar qadami

feat/react-ui branch'ida «Vazifalar qadami: butun UI React'da» bo'limidagi o'zgarishlarni kiriting: src/komponentlar/TemaTanlov.tsx, App.tsx ga <TemaTanlov /> va <Izoh />, tema-tanlash.ts (temaniQolla, saqlanganTanlov, tanlovniSaqla, tizimTemasiniKuzat), asosiy.ts, index.html, main.tsx ni o'chirish. Tekshiruvlar, commit, PR.

Yechim
  • npm run knip — main.tsx o'chgach Izoh.tsx faqat App dan import qilinadi; eski temaniUla eksporti qolib ketsa, knip "unused export" deydi.
  • Brauzerda: tema tugmalari sahifa pastida, tanlov sahifa yangilanganda saqlanadi, <html data-tema> almashadi. JavaScript o'chirilsa — tugmalar umuman yo'q (v6 da hidden bilan yashirilgan edi).
  • npm run build — JS+CSS jami taxminan 261 KB (gzip ~83 KB); index.html dan main.tsx skripti yo'qolgan.

Yakuniy kod — feat: to'liq React UI — tema tanlovi va izoh App ichida commit'i.

5-mashq: Vercel'ga deploy (o'z hisobingiz bilan)

«Vercel'ga deploy» bo'limidagi qadamlarni bajaring va «Deploy'dan keyin tekshiring» ro'yxatini to'ldiring. Natijani README.md ga yozing: Vercel manzili va Lighthouse ballari. Keyin feat/ branch'ida kichik o'zgarish qilib PR oching va Vercel bergan preview manzilni oching.

Yechim

Kutiladigan natija: manzil https://<loyiha>.vercel.app, Response Headers'da CSP (frame-ancestors 'none' bilan), x-content-type-options: nosniff, referrer-policy, hash'li JS'da immutable. Lighthouse mobil — 97–99 / 100 / 100 / 100 atrofida (tarmoq va qurilmaga qarab farq qiladi). PR'da Vercel botining izohi va preview havolasi paydo bo'ladi. Build yiqilsa — Vercel loglarida xato: ko'pincha npm ci (lock-fayl) yoki tsc -b tip xatosi. Lokal npm run build toza bo'lsa, Vercel'da ham toza bo'lishi kerak — Node versiyasi .nvmrc/engines da (Vercel sozlamasida Node 24 tanlanganini tekshiring).

11. Real ishda

  • Bu dars — React intervyu topshirig'ining aniq nusxasi. "TODO ilova yozing: qo'shish, o'chirish, filtr, tahrir" — eng ko'p beriladigan uy vazifasi. Baholanadigan narsalar: state tuzilmasi, key, o'zgarmas yangilash, komponentlarga bo'lish, kirish imkoniyati.
  • "React'ga o'tamizmi?" — real loyihalarda shunday qaror o'lchov bilan qilinadi: hajm, tezlik, jamoa. Biz qilgan taqqoslash — aynan shu ishning kichik nusxasi.
  • Vercel, Netlify, Cloudflare Pages — frontend deploy uchun eng keng tarqalgan xizmatlar. Preview deploy'lar kod ko'rigining odatiy qismi.
  • Portfolio: vazifalar (React, TypeScript, testlar, CSP, PWA) — Vercel manzili bilan rezyumega qo'yiladi.

Xulosa

  • Kichik ilova: sof funksiyalar (model) + minimal state (tasks, filter, editId) + hosilaviy qiymatlar + komponentlar.
  • Forma: yangi vazifa — uncontrolled + FormData; tahrir — controlled; key={t.id} tahrirni saqlaydi.
  • vazifalar: tema tanlovi va izoh App ichida, index.html da faqat <h1> va <div id="ilova">; useState(fn) — lazy init.
  • O'lchov: JS+CSS 14.3 → 83.1 KB (gzip, 84% — react-dom), bitta katak 47–77 → 19–20 ms, qidiruv biroz sekinroq, Lighthouse 100 → 97–98.
  • Vercel: vercel.json — build, SPA rewrite, CSP va kesh sarlavhalari; deploy — o'z hisobingiz bilan, har PR — preview.

Keyingi dars: Hooklar qoidalari va nega ular aynan shunday — hooklar nega faqat yuqori darajada va shartsiz chaqiriladi: React ularni chaqiruv tartibi bo'yicha qanday eslab qoladi.

Manbalar

  • React: "Thinking in React" — react.dev/learn/thinking-in-react
  • React: useState — "Avoiding recreating the initial state" (lazy init) — react.dev/reference/react/useState
  • Vercel: "Vite on Vercel", "Project configuration (vercel.json)", "Headers", "Rewrites" — vercel.com/docs
  • Lighthouse 13.5.0, web-vitals — o'lchovlar 2026-10-12
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Amaliy: birinchi to'liq React mini-ilova — vazifalar ro'yxati va Vercel — IlmHamroh