IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar2/36-dars23 daqiqa
Mundarija (36)

React Router I: o'rnatish, marshrutlar, Link va NavLink

Qisqacha: React Router — React uchun eng ko'p ishlatiladigan router kutubxonasi. npm i react-router bilan o'rnatiladi (8-versiyada react-router-dom paketi yo'q). Marshrutlar obyektlar massivi bilan yoziladi: { path: "/menyu", element: <Menu /> }. Ulardan createBrowserRouter(marshrutlar) router yasaydi, <RouterProvider router={router} /> uni chizadi. Sahifalar orasida <Link to="/menyu"> bilan o'tiladi — sahifa qayta yuklanmaydi. <NavLink> esa joriy sahifa havolasiga aria-current="page" va active klassini o'zi qo'yadi.

Bu darsda

  • React Router 8.4.0 ni o'rnatasiz va uning bundle narxini raqam bilan bilasiz.
  • Marshrutlarni obyekt sifatida yozib, createMemoryRouter/createBrowserRouter bilan ulaysiz.
  • Link va NavLink bilan menyu yasaysiz, joriy sahifani aria-current bilan belgilaysiz.
  • Declarative va data mode farqini va kurs nega data mode'ni tanlaganini tushuntira olasiz.
  • vazifalar ni uch sahifaga bo'lasiz: ro'yxat, statistika, sozlamalar — va commit qilasiz.

Oldin bilishingiz kerak: SPA routing qanday ishlaydi, Semver va versiya diapazonlari, React'da accessibility asoslari.

1. Nega bu kerak?

O'tgan darsda router'ni o'zimiz yozdik: manzil — state, Link — preventDefault va navigate, tarix — massiv. 40 qatorga sig'di. Lekin vazifalar ga yana ko'p narsa kerak bo'ladi:

  • /vazifa/2 kabi parametrli manzillar;
  • umumiy qism (menyu) va uning ichida almashadigan sahifa;
  • sahifa ochilishidan oldin ma'lumot yuklash, xatoni ushlash;
  • ?filtr=faol ni o'qish va yozish, sahifa almashganda fokus va aylantirish.

Bularning har birini o'zimiz yozsak — o'nlab tuzoq. Ctrl + bosish, ilova papkasi (/vazifalar/), «Orqaga» bosilganda qaysi yozuvga qaytish… React Router — shu ishlarni bajaradigan kutubxona. Uni Remix jamoasi yozadi, birinchi versiyasi 2014-yilda chiqqan. Bu darsda — asosi: o'rnatish, marshrutlar, havolalar.

Hayotiy o'xshatish: «Bahor» oshxonasining menyusi. Taom nomi (manzil) va oshpaz pishiradigan taom (sahifa) — juftlik. O'tgan darsda menyuni qo'lda, qog'ozga yozdik. Endi — tayyor buyurtma tizimi: menyu bir joyda, buyurtma qabul qilish, ofitsiant va kassa o'zi ishlaydi.

2. O'rnatish va narxi

2.1 Bitta paket: react-router

bash
npm i --save-exact react-router@8.4.0

package.json da:

json
"dependencies": {
  "react": "19.3.0",
  "react-dom": "19.3.0",
  "react-router": "8.4.0",
  "web-vitals": "6.2.3"
}

--save-exact — ^ siz, aniq versiya (Semver). Kursda hamma kutubxona shunday. react-router 8.4.0 — 2026-09-15 da chiqqan. 8-versiya 2026-06-17 dan beri asosiy (latest), 7.x — faqat xavfsizlik tuzatishlari oladigan qo'llab-quvvatlash tarmog'i.

Diqqat: Eski maqola va videolarda npm i react-router-dom ni ko'rasiz. 6-versiyagacha DOM qismi alohida paket edi. 7-versiyada hammasi react-router ga birlashdi, react-router-dom faqat eski importlar buzilmasin deb qoldirildi. 8-versiyada react-router-dom olib tashlangan (CHANGELOG, v8.0.0). Eski kodda ko'rsangiz: "react-router-dom" → "react-router", RouterProvider — "react-router/dom" dan yoki "react-router" dan.

2.2 Bepul emas: +29.4 KB

Kanonda o'lchandi: vazifalar brauzer yuklaydigan JS va CSS, gzip bilan:

vazifalar: JS + CSS hajmi (gzip)
  • v8 (router yo'q)90,7 KB
  • + React Router 7.18.4sinov120,4 KB
  • + React Router 8.4.0 (kanon)120,1 KB

Manba: O'lchandi: vazifalar v8 va holat 02, gzip -9, natijalar/tekshiruv-00.txt, tekshiruv-02.txt, 02-hajm-rr7.txt, 2026-10-13

Router 29.4 KB qo'shdi — butun ilovaning uchdan biridan ko'pi. 7 va 8 versiyalar hajmi deyarli bir xil (120 402 va 120 120 bayt). Bu ikki narsani anglatadi. Birinchidan, router "tekin" emas: sekin internetda telefon bu baytlarni yuklaydi va bajaradi. Ikkinchidan, uch sahifali ilova uchun bu narx o'zini oqlaydi, chunki keyingi darslarda shu paketdan parametrlar, loader, xatolar va URL holati ham olinadi. Kichikroq muqobil — TanStack Router, o'sha darsda o'lchab solishtiramiz.

3. Uch rejim

React Router'ni uch xil ishlatish mumkin. Hujjat ularni rejim (mode) deydi. Har keyingisi oldingisiga imkoniyat qo'shadi:

Rejim Asosi Nima beradi
Declarative <BrowserRouter>, <Routes>, <Route> manzil → komponent, havolalar
Data createBrowserRouter + <RouterProvider> + loader, action, xato sahifasi
Framework Vite plagini, routes.ts + tiplar, kodni bo'lish, SSR

Declarative mode — marshrutlar JSX ichida, komponentlar daraxtining bir qismi. Data mode — marshrutlar React'dan tashqarida, oddiy obyektlar massivi. Router ularni oldindan biladi, shuning uchun sahifa chizilishidan oldin ma'lumot yuklay oladi (data mode darsida). Framework mode — data mode ustiga Vite plagini va fayl konvensiyasi (framework mode darsida o'lchaymiz).

Kurs va vazifalar — data mode. Sabab: 5-darsdan loader va action kerak bo'ladi, declarative mode'da ular yo'q. Data mode'ning g'oyalari esa (marshrut obyekti, loader) Next.js qismida ham uchraydi. Declarative mode'ni ham ko'ramiz — eski loyihalarda u ko'p.

4. Birinchi router — data mode

4.1 Marshrut obyekti

Marshrut — oddiy obyekt. Hozircha ikki maydon:

  • path — manzil ("/menyu");
  • element — shu manzilda chiziladigan JSX (<Menu />).

Ulardan massiv tuziladi va router yasaladi.

4.2 Natija oynasida: createMemoryRouter

O'tgan darsda aytganimizdek, natija oynasida manzil satri yo'q (about:srcdoc). Shuning uchun jonli misollarda — createMemoryRouter: tarix xotirada, xuddi biz yozgan entries va index kabi. Loyihada esa createBrowserRouter — keyingi bo'limda. Qolgan hamma narsa bir xil.

«Bahor»ning ikki sahifasi. Mehmon «Menyu» havolasini bosadi:

jsx
import { useEffect } from "react";
import {
  createMemoryRouter,
  Link,
  RouterProvider,
} from "react-router";

function Home() {
  useEffect(() => {
    console.log(document.querySelector("a").outerHTML);
    console.log("Home: ekranga chiqdi");
    return () => console.log("Home: olib tashlandi");
  }, []);
  return (
    <div>
      <h1>Bahor oshxonasi</h1>
      <Link to="/menyu">Menyu</Link>
    </div>
  );
}

function Menu() {
  return (
    <div>
      <h1>Menyu</h1>
      <p>Osh — 35 000 so'm</p>
      <Link to="/">Bosh sahifa</Link>
    </div>
  );
}

// Natija oynasida manzil satri yo'q — xotiradagi router
const router = createMemoryRouter(
  [
    { path: "/", element: <Home /> },
    { path: "/menyu", element: <Menu /> },
  ],
  { initialEntries: ["/"] },
);

export default function App() {
  console.log("App chizildi");
  return <RouterProvider router={router} />;
}

Konsolda:

text
App chizildi
<a href="/menyu" data-discover="true">Menyu</a>
Home: ekranga chiqdi
Home: olib tashlandi

Ekranda:

text
Menyu
Osh — 35 000 so'm
Bosh sahifa

Konsoldan to'rt narsa o'qiladi:

  1. App chizildi — bir marta. Sahifa almashganda App qayta chizilmadi. Router o'z ichidagi state'ni o'zgartirdi va faqat sahifa qismini almashtirdi.
  2. Link — oddiy <a href="/menyu">. O'tgan darsdagi Link imiz bilan bir xil g'oya: havola havola bo'lib qoladi. data-discover — framework mode uchun belgi (marshrutni oldindan topish), data mode'da ta'siri yo'q.
  3. Home: olib tashlandi — eski sahifa unmount bo'ldi: effekt tozalanishi ishladi (useEffect III). Sahifa ichidagi state ham yo'qoldi. Qaytib kelsangiz, Home noldan o'rnatiladi.
  4. Ekranda — faqat Menu. Har manzilda bitta mos marshrutning element i chiziladi.

initialEntries: ["/"] — boshlang'ich tarix: qaysi manzildan boshlash. Uni ["/menyu"] qilib «Ishga tushir» ni bosing — misol menyudan ochiladi. Bu chuqur havolaning xotiradagi o'xshashi.

Tekshirib ko'ring: Home ichida useState bilan hisoblagich bo'lsa va uni 3 ga yetkazib, «Menyu» ga o'tib, qaytsangiz — hisoblagich nechada bo'ladi?

Javob

0 da. Sahifa almashganda Home daraxtdan olib tashlandi — uning state'i ham ketdi (State I: state komponent o'rnatilgan joyga bog'langan). Qaytganda Home yangidan o'rnatiladi. Sahifalar orasida saqlanishi kerak bo'lgan qiymat sahifadan yuqorida turishi kerak: umumiy qismda (layout), URL'da yoki store'da — bularni keyingi darslarda ko'ramiz.

4.3 Loyihada: createBrowserRouter

Haqiqiy loyihada main.jsx:

jsx
import { createRoot } from "react-dom/client";
import { createBrowserRouter } from "react-router";
import { RouterProvider } from "react-router/dom";
import { Home } from "./Home.jsx";
import { Menu } from "./Menu.jsx";

// Router — bir marta, komponentdan TASHQARIDA
const router = createBrowserRouter([
  { path: "/", element: <Home /> },
  { path: "/menyu", element: <Menu /> },
]);

createRoot(document.getElementById("root")).render(
  <RouterProvider router={router} />,
);

Uch izoh:

  • createBrowserRouter — tarix brauzerniki: manzil satri, pushState, popstate. Jonli misoldagi createMemoryRouter bilan bir xil qabul qiladi, faqat initialEntries o'rniga hozirgi manzil (location) dan boshlaydi.
  • Router komponentdan tashqarida yaratiladi. Hujjat buni alohida yozadi: data router'ni React state'ida saqlamang. Komponent ichida bo'lsa, har renderda yangi router — tarix va holat yo'qoladi.
  • RouterProvider hujjatda "react-router/dom" dan. Bu variant React DOM'ning flushSync ini router'ga beradi (navigatsiyani sinxron bajarish imkoniyati uchun). "react-router" dagisi ham ishlaydi — vazifalar kanonida shunisi.

Diqqat: Natija oynasida createBrowserRouter "Invalid URL" xatosi bilan yiqiladi: iframe manzili about:srcdoc, undan haqiqiy yo'l yasab bo'lmaydi. Shuning uchun darsdagi har jonli misol createMemoryRouter yoki MemoryRouter da. O'z loyihangizga ko'chirganda bitta qatorni almashtirasiz.

Link <a href> chizadi, lekin bosilganda router'ga o'tadi. Oddiy <a href="/menyu"> ni ilova ichida yozsangiz, ishlaydi — lekin sahifa butunlay qayta yuklanadi:

<Link to="/menyu"> <a href="/menyu">
Serverga so'rov yo'q bor (butun HTML)
JS holati, keshdagi ma'lumot saqlanadi yo'qoladi
Ekran faqat sahifa qismi almashadi oq ekran, noldan

Ilova ichidagi o'tishlar — doim Link. Boshqa saytga havola — oddiy <a>: Link absolyut manzilni (https://…) baribir oddiy havola kabi qoldiradi.

Prop Nima qiladi
to qayerga: "/menyu" yoki "/menyu?saralash=narx"
replace tarixga yangi yozuv qo'shmaydi, joriysini almashtiradi
state manzilga ko'rinmas qo'shimcha ma'lumot (history.state ga)
reloadDocument ataylab oddiy yuklanish (masalan, boshqa ilovaga)

replace History API darsidagi replaceState ning o'zi. Masalan, kirish formasidan keyin «Orqaga» yana formaga qaytarmasligi kerak — buni himoyalangan marshrutlar darsida ishlatamiz. Nisbiy to ("osh", "..") — ichma-ich marshrutlar darsida.

6. Umumiy qism: layout va <Outlet />

Menyu har sahifada takrorlanmasligi uchun uni bir joyga chiqaramiz. Marshrut children (bolalar) olishi mumkin. Ota marshrutning element i umumiy qismni chizadi, bola sahifa esa uning ichidagi <Outlet /> o'rnida chiziladi. Outlet — "rozetka": bola sahifa shu joyga ulanadi.

jsx
{
  path: "/",
  element: <Layout />,          // menyu + <Outlet />
  children: [
    { index: true, element: <Home /> },  // aynan "/"
    { path: "menyu", element: <Menu /> }, // "/menyu"
  ],
}

index: true — ota manzilining o'zi (/). Bolaning path i / siz yoziladi — ota yo'liga qo'shiladi. Shunday ota — layout marshrut. Hozircha shu yetadi. Ichma-ich daraxt, layout va index ning hamma qoidalari — React Router II da.

7.1 aria-current va active

Menyuda joriy sahifa ajralib turishi kerak. Ko'radigan odam uchun — qalin yozuv yoki chiziq. Malika (ekran o'quvchi) uchun — "joriy sahifa" degan so'z. Ikkalasini bitta atribut beradi: aria-current="page" (React'da accessibility). NavLink — joriy manzilga mos bo'lsa, shu atributni va active klassini o'zi qo'yadigan Link.

Quyidagi misolda CurrentLink — har sahifa almashganda DOM'dan aria-current li havolani topib, konsolga yozadi. U useLocation() dan foydalanadi: bu hook joriy manzilni beradi (to'liq — React Router III da):

jsx
import { useEffect } from "react";
import {
  createMemoryRouter,
  NavLink,
  Outlet,
  RouterProvider,
  useLocation,
} from "react-router";

function CurrentLink() {
  const { pathname } = useLocation();
  useEffect(() => {
    const link = document.querySelector('[aria-current="page"]');
    console.log(pathname, "→", link.textContent, link.className);
  }, [pathname]);
  return null;
}

function Layout() {
  return (
    <div>
      <nav aria-label="Bo'limlar">
        <NavLink to="/" end>Bosh sahifa</NavLink>
        {" | "}
        <NavLink to="/menyu">Menyu</NavLink>
      </nav>
      <CurrentLink />
      <Outlet />
    </div>
  );
}

const router = createMemoryRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <h1>Xush kelibsiz</h1> },
      { path: "menyu", element: <h1>Menyu: osh, manti</h1> },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
/ → Bosh sahifa active
/menyu → Menyu active

Ekranda:

text
Bosh sahifa | Menyu
Menyu: osh, manti

Bosishdan keyin aria-current va active «Bosh sahifa» dan «Menyu» ga ko'chdi — biz bitta if ham yozmadik. Menyu (Layout) esa joyida qoldi, faqat <Outlet /> o'rni almashdi.

7.2 Uslub: atributga qarab

CSS'da joriy havolani qanday tanlash kerak? Ikki yo'l bor: .active klassi yoki [aria-current="page"] atributi. Ikkinchisi yaxshiroq: vizual belgi va ekran o'quvchi uchun belgi — bitta manba. Biri bor, ikkinchisi yo'q bo'lib qolishi mumkin emas. vazifalar da aynan shunday:

css
.bolimlar a[aria-current="page"] {
  font-weight: 700;
  text-decoration-thickness: 3px;
}

Klass kerak bo'lsa (masalan Tailwind), className ga funksiya berish mumkin: className={({ isActive }) => isActive ? "font-bold" : ""}. style va children ham shunday funksiya qabul qiladi. Funksiya oladigan obyektda isActive dan tashqari isPending ham bor — data mode'da sahifa yuklanayotganda true (loader'lar bilan, 5-darsda).

7.3 end: ichma-ich yo'l tuzog'i

NavLink sukut bo'yicha boshlanishi bo'yicha solishtiradi. /menyu havolasi /menyu da ham, /menyu/osh da ham joriy hisoblanadi. Bo'lim menyusi uchun bu to'g'ri: osh sahifasida turganda "Menyu" bo'limi belgilangan bo'lsin. Lekin ba'zan noto'g'ri. Uch havola — bo'lim, ichki sahifa va bosh sahifa. [ ] — joriy belgisi (children funksiyasi bilan):

jsx
import {
  createMemoryRouter,
  NavLink,
  Outlet,
  RouterProvider,
} from "react-router";

function Item({ to, end, children }) {
  return (
    <NavLink to={to} end={end}>
      {({ isActive }) => (isActive ? `[${children}]` : children)}
    </NavLink>
  );
}

function Layout() {
  return (
    <div>
      <nav>
        <Item to="/">Bosh</Item> <Item to="/menyu">Menyu</Item>{" "}
        <Item to="/menyu/osh">Osh</Item>
      </nav>
      <Outlet />
    </div>
  );
}

const router = createMemoryRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <h1>Bosh sahifa</h1> },
      { path: "menyu", element: <h1>Menyu</h1> },
      { path: "menyu/osh", element: <h1>Osh — 35 000 so'm</h1> },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
Bosh [Menyu] [Osh]
Osh — 35 000 so'm

/menyu/osh da ikki havola joriy: «Menyu» ham, «Osh» ham. Ikkalasida aria-current="page" — Malika ikki "joriy sahifa"ni eshitadi. Agar «Menyu» faqat aynan /menyu da belgilanishi kerak bo'lsa — <Item to="/menyu" end>. end — "faqat manzil shu yerda tugasa".

«Bosh» esa /menyu/osh da belgilanmadi, garchi har manzil / bilan boshlansa ham. react-router 8.4.0 kodida ildiz (/) uchun alohida shart bor: / dan keyingi belgi ham / bo'lishi kerak. Baribir hujjat va kanon <NavLink to="/" end> yozadi — niyat aniq ko'rinadi va versiyaga bog'liq bo'lmaydi.

Tekshirib ko'ring: Misoldagi «Menyu» ga end qo'shsangiz va «Osh» ni bossangiz, ekranning birinchi qatori qanday bo'ladi?

Javob

Bosh Menyu [Osh]. end bilan «Menyu» faqat /menyu da joriy, /menyu/osh da emas. Endi joriy havola — bitta, aria-current="page" ham bitta. Qaysi biri to'g'ri — dizayn qarori: "bo'lim" menyusi uchun end siz, "aniq sahifa" menyusi uchun end bilan.

8. Declarative mode: <Routes> va <Route>

Ko'p mavjud loyihalarda marshrutlar JSX ichida yozilgan. O'sha ikki sahifa declarative mode'da:

jsx
import { Link, MemoryRouter, Route, Routes } from "react-router";

function App() {
  return (
    <MemoryRouter initialEntries={["/"]}>
      <nav>
        <Link to="/">Bosh sahifa</Link>
        {" | "}
        <Link to="/menyu">Menyu</Link>
      </nav>
      <Routes>
        <Route path="/" element={<h1>Xush kelibsiz</h1>} />
        <Route path="/menyu" element={<h1>Menyu: osh, manti</h1>} />
      </Routes>
    </MemoryRouter>
  );
}

Ekranda:

text
Bosh sahifa | Menyu
Menyu: osh, manti

<MemoryRouter> — createMemoryRouter ning komponent ko'rinishi, loyihada uning o'rnida <BrowserRouter>. <Routes> — ichidagi <Route> lardan joriy manzilga eng mosini tanlab chizadi. <Route path element> — bizning { path, element } obyektimizning JSX ko'rinishi.

Declarative Data
Marshrutlar qayerda JSX ichida, render paytida obyektlar massivi, React'dan tashqarida
Router <BrowserRouter> createBrowserRouter + <RouterProvider>
Loader, action, errorElement yo'q bor
Qayerda ko'rasiz eski va oddiy loyihalar yangi loyihalar, bu kurs

Asosiy farq — birinchi qatorda. Data mode'da router barcha marshrutlarni oldindan biladi. Shu sababli sahifani chizishdan oldin uning ma'lumotini yuklashni boshlay oladi. Declarative mode'da marshrut faqat chizilganda ma'lum bo'ladi.

Maslahat: Eski kodda yana quyidagilarni ko'rasiz: <Switch> (5-versiya, hozir <Routes>), component={Menu} va exact (5-versiya; hozir element va sukut bo'yicha aniq moslik), useHistory (hozir useNavigate). Bu nomlarni ko'rsangiz — kod 2021-yildan oldin yozilgan.

9. Ko'p uchraydigan xatolar

jsx
import { Link } from "react-router";

function App() {
  return <Link to="/menyu">Menyu</Link>;
}

Konsolda:

text
Uncaught TypeError: Cannot destructure property 'basename' of 'React$111.useContext(...)' as it is null.

Tarjimasi: "React$111.useContext(...) dan basename xususiyatini (property) ajratib bo'lmaydi, chunki u null." Xabar Link haqida hech narsa demaydi — shuning uchun chalg'itadi. Nega basename? Link birinchi qatorda router kontekstidan basename ni oladi (Destructuring). Router yo'q — kontekst null, null dan xususiyat olib bo'lmaydi. React$111 — kutubxonalar bitta faylga yig'ilganda React ga berilgan nom; o'z loyihangizda boshqacha bo'ladi (masalan React3). Qoida: basename, navigator, useContext(...) … null haqidagi xato — deyarli doim "router'dan tashqarida" degani.

Hooklar aniqroq gapiradi. useLocation() ni router'dan tashqarida chaqirsangiz:

text
Uncaught Error: useLocation() may be used only in the context of a <Router> component.

"useLocation() faqat <Router> komponenti kontekstida ishlatilishi mumkin." Davosi ikkalasida bir xil: komponent RouterProvider (yoki MemoryRouter/BrowserRouter) ichida chizilsin.

9.2 Boshqa xatolar

Belgi Sabab Davosi
Vite: Failed to resolve import "react-router-dom" 8-versiyada bu paket yo'q "react-router" dan import
har bosishda oq ekran, Network'da HTML so'rovi <a href> ilova ichida <Link to>
sahifa almashganda hamma narsa "unutiladi" router komponent ichida yaratilgan createBrowserRouter — modul darajasida
ikki havolada aria-current end siz ota bo'lim havolasi kerakli joyda end
natija oynasida "Invalid URL" createBrowserRouter jonli misolda createMemoryRouter
bola sahifa chiqmaydi, faqat layout layout'da <Outlet /> yo'q <Outlet /> qo'shing

10. Vazifalar qadami: uch sahifa (holat 02)

O'tgan darsda feat/sahifalar branch'ida server tomonini tayyorladik. Endi router.

10.1 Marshrutlar — src/marshrutlar.tsx

Kanondan, aynan:

tsx
// marshrutlar.tsx — sahifalar xaritasi (19/#02). React Router 8, "data
// mode": marshrutlar — obyektlar (path → element), router ularni
// createBrowserRouter bilan oladi (main.tsx). Layout marshrut (Qobiq) —
// umumiy qism, bolalari uning <Outlet /> o'rnida. errorElement — shu
// marshrut yoki bolalari xato tashlasa
import type { RouteObject } from "react-router";
import { Qobiq } from "./sahifalar/Qobiq.tsx";
import { RoyxatSahifasi } from "./sahifalar/RoyxatSahifasi.tsx";
import { SozlamalarSahifasi } from "./sahifalar/SozlamalarSahifasi.tsx";
import { StatistikaSahifasi } from "./sahifalar/StatistikaSahifasi.tsx";
import { TopilmadiSahifasi } from "./sahifalar/TopilmadiSahifasi.tsx";
import { XatoSahifasi } from "./sahifalar/XatoSahifasi.tsx";

export const marshrutlar: RouteObject[] = [
  {
    path: "/",
    element: <Qobiq />,
    errorElement: <XatoSahifasi />,
    children: [
      // index — ota manzilining o'zi ("/")
      { index: true, element: <RoyxatSahifasi /> },
      { path: "statistika", element: <StatistikaSahifasi /> },
      { path: "sozlamalar", element: <SozlamalarSahifasi /> },
      // "*" — boshqa hech biriga mos kelmagan manzil
      { path: "*", element: <TopilmadiSahifasi /> },
    ],
  },
];

RouteObject — marshrut obyektining TypeScript turi (TypeScript qismidagi kabi kutubxona turlari). Ikki yangi narsa bir gap bilan: "*" — hech bir marshrutga mos kelmagan manzil (404 sahifa), errorElement — sahifa xato tashlasa, uning o'rnida chiziladigan sahifa. Ikkalasi ham to'liq — 3- va 5-darslarda.

Marshrutlar alohida faylda — "sahifalar xaritasi" bitta joyda ko'rinadi. sahifalar/ papkasida har sahifa — alohida komponent. RoyxatSahifasi — 18-qismdagi butun App ni o'raydi: ro'yxat sahifasi o'zgarmadi, faqat endi u bitta sahifa.

10.2 Router — src/main.tsx

tsx
// Router (19/#02): History API bilan (manzil — oddiy yo'l, # siz).
// basename — ilova papkasi (Pages'da "/vazifalar", vite.config.ts base):
// marshrutlarda "/statistika" yoziladi, manzilda /vazifalar/statistika.
// Marshrutlarda loader yo'q — birinchi chizish sinxron qoladi (CLS 0)
const router = createBrowserRouter(marshrutlar, {
  basename: import.meta.env.BASE_URL.replace(/\/$/, "") || "/",
});

basename — ilova joylashgan papka. O'tgan darsda Vite'ga base: "/vazifalar/" berdik — fayllar uchun. Router ham papkani bilishi kerak: manzil /vazifalar/statistika, marshrut esa "statistika". basename bilan router papkani o'zi qo'shadi va olib tashlaydi. Link to="/statistika" → href="/vazifalar/statistika". replace(/\/$/, "") — oxiridagi / ni olib tashlaydi ("/vazifalar/" → "/vazifalar"), Vercel'da esa "/" qoladi.

createRoot(...).render(...) ichida avvalgi <Ilova /> o'rniga endi <RouterProvider router={router} />. U hamon flushSync ichida: loader yo'q, demak router birinchi sahifani darhol, sinxron chizadi — sahifa ro'yxat bilan birga paydo bo'ladi (CLS 0, 18-qism).

10.3 Umumiy qism — sahifalar/Qobiq.tsx

Qobiq — layout marshrut. Unga 18-qismdagi skip link va foydalanuvchi menyusi App dan ko'chdi. Bo'limlar menyusi:

tsx
      {/* NavLink — joriy sahifa havolasiga aria-current="page" qo'yadi
          (ekran o'quvchi "joriy sahifa" deydi, CSS shu atributga
          qaraydi). end — "/" faqat aynan ro'yxat sahifasida joriy */}
      <nav className="bolimlar" aria-label="Bo'limlar">
        <ul>
          <li>
            <NavLink to="/" end>
              Ro'yxat
            </NavLink>
          </li>
          <li>
            <NavLink to="/statistika">Statistika</NavLink>
          </li>
          <li>
            <NavLink to="/sozlamalar">Sozlamalar</NavLink>
          </li>
        </ul>
      </nav>
      <FoydalanuvchiMenyusi />
      <Outlet context={kontekst} />

<nav aria-label="Bo'limlar"> — Malika "Bo'limlar navigatsiyasi" ni eshitadi va unga bir tugma bilan o'ta oladi. <Outlet context={kontekst}> — layout sahifaga qiymat uzatadi (bu yerda — sarlavha ref'i, skip link uchun). U useOutletContext() bilan olinadi — React Router II da batafsil. CSS — «Uslub: atributga qarab» bo'limidagi qoida.

10.4 Natija

Testlar: node 174, Vitest 49 (App testlari router'siz ham ishlaydi). Hajm — «Bepul emas» bo'limidagi 120.1 KB. Yangi brauzer tekshiruvi e2e-19.mjs — 19 ta, hammasi dist/ da, CSP ostida (brauzer-tekshiruv/natija-19-02.txt dan):

text
✅ menyu: uch bo'lim, joriy — Ro'yxat (aria-current): ["Ro'yxat*","Statistika","Sozlamalar"]
✅ Statistika: sahifa qayta yuklanmadi (SPA), sarlavha va title: [true,"Statistika","Statistika — Vazifalar"]
✅ Orqaga: ro'yxat, filtr URL'da qoldi: ["/?filtr=faol",["1","3"],true]
✅ 404: noma'lum manzil — «Sahifa topilmadi», menyu bor: ["Sahifa topilmadi","«/yoq/sahifa» manzilida sahifa yo'q.",3]
✅ Pages: /vazifalar/statistika — HTTP 404 (404.html), lekin sahifa ochildi: [404,"Statistika"]
✅ Pages oflayn: /vazifalar/statistika — keshdan: "Statistika"

"Sahifa qayta yuklanmadi" qanday tekshirildi? Tekshiruvchi oynaga belgi qo'yadi (window.__belgi = 1), havolani bosadi va belgi joyidami — qaraydi. Sahifa qayta yuklanganda window yangidan yaratiladi va belgi yo'qoladi. Bu — SPA'ning eng oddiy isboti.

O'tgan darsdagi chuqur havola muammosi ham hal bo'ldi. Pages kabi serverda, /vazifalar/ papkasida (natijalar/02-chuqur-havola-v9.txt):

text
/vazifalar/vazifa/2 → HTTP 404 | #ilova: 19 element | h2: "Sahifa topilmadi" | title: "Sahifa topilmadi — Vazifalar"

Fayllar topildi, ilova ochildi, router manzilni o'qidi. /vazifa/2 marshruti hali yo'q — shuning uchun "Sahifa topilmadi". Bu to'g'ri javob: keyingi darsda shu marshrutni qo'shamiz. 13–18-qismning 159 ta va 18-qismning 72 ta brauzer tekshiruvi ham o'tdi — ro'yxat sahifasining xulqi o'zgarmadi.

10.5 Commit

bash
git add -A
git commit -m "feat: sahifalar — React Router 8 (data mode), layout, 404, Pages zaxirasi"

Kanonda commit tanasida o'zgarishlar ro'yxati bor: marshrutlar.tsx, sahifalar/, base — BASE_PATH, 404.html (Vite plagini), %BASE_URL%, SW navigatsiyasi, skip link va foydalanuvchi menyusi Qobiq'da. Jami 21 fayl, +405 −72 qator.

11. Mashqlar

Mashqlar kurs/mashqlar/19/02-react-router/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): bo'sh joylarni to'ldiring

  • 8-versiyada o'rnatiladigan paket: npm i
  • Link brauzerda chizadigan teg: <>
  • NavLink joriy havolaga qo'yadigan atribut: aria-current=""
  • Natija oynasida ishlaydigan router: createRouter
  • Layout'da bola sahifa chiziladigan komponent: < />
Yechim

react-router — 8-versiyada yagona paket (react-router-dom olib tashlangan). Link — oddiy <a>, shuning uchun yangi tabda ochish va nusxalash ishlaydi. aria-current="page" — ekran o'quvchi uchun "joriy sahifa". createMemoryRouter — tarix xotirada, manzil satri kerak emas. <Outlet /> — ota marshrutning "rozetkasi".

2-mashq (o'rta): «Aloqa» sahifasi

«aria-current va active» bo'limidagi misolga uchinchi sahifa qo'shing:

  • manzil /aloqa, sarlavha Tel: +998 90 000 00 00;
  • menyuda NavLink — «Aloqa»;
  • CurrentLink ham o'zgarishsiz ishlasin.

Sinov: «Aloqa» ni bosing — konsolda /aloqa → Aloqa active.

Yechim
jsx
import { useEffect } from "react";
import {
  createMemoryRouter,
  NavLink,
  Outlet,
  RouterProvider,
  useLocation,
} from "react-router";

function CurrentLink() {
  const { pathname } = useLocation();
  useEffect(() => {
    const link = document.querySelector('[aria-current="page"]');
    console.log(pathname, "→", link.textContent, link.className);
  }, [pathname]);
  return null;
}

function Layout() {
  return (
    <div>
      <nav aria-label="Bo'limlar">
        <NavLink to="/" end>Bosh sahifa</NavLink>
        {" | "}
        <NavLink to="/menyu">Menyu</NavLink>
        {" | "}
        <NavLink to="/aloqa">Aloqa</NavLink>
      </nav>
      <CurrentLink />
      <Outlet />
    </div>
  );
}

const router = createMemoryRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      { index: true, element: <h1>Xush kelibsiz</h1> },
      { path: "menyu", element: <h1>Menyu: osh, manti</h1> },
      { path: "aloqa", element: <h1>Tel: +998 90 000 00 00</h1> },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
/ → Bosh sahifa active
/aloqa → Aloqa active

Ekranda:

text
Bosh sahifa | Menyu | Aloqa
Tel: +998 90 000 00 00

Ikki joyga bitta qator: marshrut (children ga) va havola (menyuga). CurrentLink ga tegilmadi — u qaysi havola joriy ekanini DOM'dan oladi, NavLink esa atributni o'zi qo'yadi.

3-mashq (qiyin): declarative → data mode

Quyidagi ilova declarative mode'da yozilgan. Uni data mode'ga ko'chiring:

  • createMemoryRouter va RouterProvider;
  • menyu — layout marshrutda, sahifalar — <Outlet /> da;
  • havolalar — NavLink;
  • har noma'lum manzilga Sahifa topilmadi (path: "*").

Sinov uchun router initialEntries: ["/eski-menyu"] bilan boshlansin (eskirgan havola), keyin mehmon «Bosh sahifa» ni bosadi.

jsx
import { Link, MemoryRouter, Route, Routes } from "react-router";

function App() {
  return (
    <MemoryRouter initialEntries={["/"]}>
      <nav>
        <Link to="/">Bosh sahifa</Link>
        {" | "}
        <Link to="/menyu">Menyu</Link>
      </nav>
      <Routes>
        <Route path="/" element={<h1>Xush kelibsiz</h1>} />
        <Route path="/menyu" element={<h1>Menyu: osh, manti</h1>} />
      </Routes>
    </MemoryRouter>
  );
}

Ekranda:

text
Bosh sahifa | Menyu
Xush kelibsiz
Yechim
jsx
import {
  createMemoryRouter,
  NavLink,
  Outlet,
  RouterProvider,
} from "react-router";

function Layout() {
  return (
    <div>
      <nav>
        <NavLink to="/" end>Bosh sahifa</NavLink>
        {" | "}
        <NavLink to="/menyu">Menyu</NavLink>
      </nav>
      <Outlet />
    </div>
  );
}

const router = createMemoryRouter(
  [
    {
      path: "/",
      element: <Layout />,
      children: [
        { index: true, element: <h1>Xush kelibsiz</h1> },
        { path: "menyu", element: <h1>Menyu: osh, manti</h1> },
        { path: "*", element: <h1>Sahifa topilmadi</h1> },
      ],
    },
  ],
  { initialEntries: ["/eski-menyu"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
Bosh sahifa | Menyu
Xush kelibsiz

/eski-menyu hech bir marshrutga mos kelmadi — "*" ishladi, lekin menyu (layout) joyida qoldi: "*" ham Layout ning bolasi. Mehmon adashmaydi, menyudan kerakli sahifani tanlaydi. "*" ni children dan tashqariga, eng yuqori darajaga qo'ysangiz, 404 sahifa menyusiz chiqardi. vazifalar da TopilmadiSahifasi ham Qobiq ichida — xuddi shu sabab bilan.

4-mashq: vazifalar ga nazar

feat/sahifalar branch'ida «Vazifalar qadami» ni bajaring: npm i --save-exact react-router@8.4.0, marshrutlar.tsx, sahifalar/ (Qobiq, RoyxatSahifasi, StatistikaSahifasi, SozlamalarSahifasi, TopilmadiSahifasi, XatoSahifasi), main.tsx, CSS. npm run check toza bo'lsin. Keyin npm run dev da:

  1. «Statistika» ni bosing va DevTools → Network'da yangi HTML so'rovi bormi — qarang.
  2. Statistika sahifasida F5 bosing.
  3. Manzil satriga /yoq yozing.
Yechim
  1. Network'da hujjat (Doc) so'rovi yo'q — faqat sahifa qismi almashdi. Elements panelida «Statistika» havolasida aria-current="page".
  2. F5 dan keyin ham statistika: Vite dev serveri SPA zaxirasini o'zi beradi.
  3. «Sahifa topilmadi» va ««/yoq» manzilida sahifa yo'q.» — menyu bilan. npm run check — node 174, Vitest 49 test, lint, format, tip — toza. Agar format:check App.tsx ga shikoyat qilsa — npm run format (kanonda ham bir marta shunday bo'ldi).

12. Real ishda

  • Har React SPA'da: admin panel, shaxsiy kabinet, ichki tizimlar — React Router (yoki TanStack Router). Ish e'lonlarida "React Router" talablar ro'yxatida tez-tez uchraydi.
  • Versiya migratsiyasi: real loyihalarda 5, 6, 7-versiyalar aralash yashaydi. Switch, useHistory, react-router-dom ni ko'rib, versiyani aniqlash va ko'chirish — oddiy topshiriq. Rasmiy "Upgrading" qo'llanmasi bor.
  • Framework mode va Next.js: yangi loyihalarning bir qismi to'g'ridan-to'g'ri framework'dan boshlanadi. Data mode tushunchalari (marshrut, layout, loader) u yerda ham — framework mode va Next.js.
  • Intervyu savollari: "Link va <a> farqi?", "NavLink nima qo'shadi?", "Declarative va data mode farqi?", "basename nima uchun?", "Nega router komponentdan tashqarida yaratiladi?".

Xulosa

  • React Router 8.4.0: bitta paket react-router (react-router-dom yo'q), narxi +29.4 KB gzip.
  • Data mode: marshrutlar — { path, element, children } obyektlari, createBrowserRouter + <RouterProvider>; natija oynasida — createMemoryRouter.
  • Link — <a href>, sahifa qayta yuklanmaydi; sahifa almashganda eski sahifa unmount bo'ladi, layout joyida qoladi.
  • NavLink joriy havolaga aria-current="page" va active qo'yadi; CSS — atributga; end — faqat aniq manzilda.
  • Declarative mode (<Routes>, <Route>) — eski va oddiy loyihalarda; loader va action faqat data mode'da.
  • vazifalar: Qobiq + uch sahifa + 404, basename — Pages papkasi; 159 + 72 + 19 brauzer tekshiruvi o'tdi.

Keyingi dars: React Router II: dinamik, ichma-ich va layout marshrutlar — /vazifa/:id, useParams, ichma-ich <Outlet> va index marshrutlar.

Manbalar

  • React Router: "Picking a Mode", "Data Mode — Installation, Routing, Navigating" — reactrouter.com (paket ichida: node_modules/react-router/docs/start/)
  • React Router: CHANGELOG, v8.0.0 ("Remove react-router-dom package") va v8.4.0 — github.com/remix-run/react-router
  • React Router API: createBrowserRouter, createMemoryRouter, RouterProvider, Link, NavLink — api.reactrouter.com
  • WAI-ARIA 1.2: aria-current — w3.org/TR/wai-aria-1.2
  • MDN: "aria-current" — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React Router I: o'rnatish, marshrutlar, Link va NavLink — IlmHamroh