Mundarija (35)
- Bu darsda
- 1. Nega bu kerak?
- 2. Dinamik segment va useParams
- 2.1 Bitta marshrut — ko'p sahifa
- 2.2 Parametr doim satr
- 2.3 Bir nechta parametr
- 2.4 Tuzoq: parametr o'zgarsa, state qoladi
- 3. Qaysi marshrut yutadi: statik, dinamik, ixtiyoriy va *
- 3.1 Statik segment dinamikdan kuchli
- 3.2 Ixtiyoriy segment: ?
- 3.3 * — hammasini ushlovchi
- 4. Ichma-ich marshrutlar va <Outlet />
- 4.1 Bo'lim almashadi, sarlavha qoladi
- 4.2 Index marshrut — otaning "o'z" sahifasi
- 4.3 Nechta qavat bo'lsa ham
- 5. Layout marshrutlar
- 5.1 Ramka — butun ilova uchun
- 5.2 Yo'lsiz layout — ramka bor, segment yo'q
- 5.3 <Outlet context> — bolalarga ma'lumot uzatish
- 6. Vazifalar qadami: vazifa sahifasi (holat 03)
- 6.1 Nima quramiz
- 6.2 Marshrut: marshrutlar.tsx
- 6.3 Sahifa: VazifaSahifasi.tsx
- 6.4 Kontekst: vazifa-konteksti.ts
- 6.5 Qatorda havola: OddiyQator.tsx
- 6.6 Testlar va tekshiruvlar
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): qaysi marshrut?
- 2-mashq (o'rta): ikki sahifani ichma-ich qiling
- 3-mashq (qiyin): taom topilmasa va kontekst
- 4-mashq: Vazifalar qadami
- 9. Real ishda
- Xulosa
- Manbalar
React Router dinamik marshrutlar: useParams, ichma-ich marshrutlar, Outlet va layout
Qisqacha:
path: "/taom/:slug"— bitta marshrut yuzlab sahifaga xizmat qiladi::slugo'rniga manzildagi istalgan qiymat tushadi, komponent uniuseParams()bilan o'qiydi (doim satr). Marshrutlar ichma-ich yoziladi (children). Ota marshrut umumiy qismni chizadi, bolasi esa otaning<Outlet />turgan joyida chiqadi.index: true— ota manzilining o'zi,"*"— boshqa hech biriga mos kelmagan manzil. Yo'li yo'q marshrut (layout) manzilga hech narsa qo'shmaydi, faqat umumiy ramka beradi.
Bu darsda
:slugva:idkabi dinamik segment yozasiz va qiymatiniuseParamsbilan o'qiysiz. Parametr nega doim satr ekanini tushunasiz.- Statik, dinamik, ixtiyoriy (
?) va*segmentlardan qaysi biri "yutishini" bilasiz. - Ichma-ich marshrutlar,
<Outlet />va index marshrut bilan sahifa ichida bo'limlar qurasiz. - Layout marshrutni (yo'lli va yo'lsiz) ajratasiz va
<Outlet context>orqali bolalarga ma'lumot berasiz. vazifalarga/vazifa/:idsahifasini qo'shasiz: ro'yxatdagi har qatorda «Batafsil».
Oldin bilishingiz kerak: React Router I: sozlash, Route va Link, SPA routing qanday ishlaydi, State'ning saqlanishi va key bilan reset, Context I.
1. Nega bu kerak?
Oldingi darsda vazifalar ga uchta sahifa qo'shdik: ro'yxat, statistika va sozlamalar. Har biri — marshrutlar.tsx da bitta qator. Uchta sahifaga uchta qator — muammo yo'q.
Endi Jasur aka «Bahor» uchun yangi talab qo'ydi. Menyudagi 30 ta taomning har biri o'z sahifasiga ega bo'lsin, .../taom/osh havolasi osh sahifasini ochsin. Nima, 30 ta marshrut va 30 ta deyarli bir xil komponent yozamizmi?
Ikkinchi talab: taom sahifasida ikki bo'lim — «Tarkibi» va «Sharhlar». Sarlavha va narx tepada qoladi, faqat pastki qism almashadi.
Hayotiy o'xshatish: restoranning stol raqamlari. Ofitsiant har stol uchun alohida qoida yodlamaydi. U bitta qolipni biladi: "Stol № ___ ga choy olib bor". Raqam o'zgaradi, ish bir xil. Dinamik marshrut — ana shu qolip. Ikkinchi talab uchun esa rasm ramkasini eslang. Ramka devorda qoladi, ichidagi rasm almashadi. Layout — ramka, <Outlet /> — rasm turadigan joy.
2. Dinamik segment va useParams
2.1 Bitta marshrut — ko'p sahifa
Segment — manzilning ikki / orasidagi bo'lagi: /taom/osh da ikkita segment bor, taom va osh. Dinamik segment (dynamic segment) — ikki nuqta bilan boshlanadigan segment: :slug. U "shu joyga istalgan qiymat tushishi mumkin, uni slug deb ata" degani. /taom/osh, /taom/manti va /taom/lagmon — uchalasi ham /taom/:slug ga mos keladi.
Komponent qiymatni useParams() hook'i bilan o'qiydi. U manzildagi parametrlarni obyekt qilib qaytaradi: { slug: "manti" }.
Bir eslatma. Saytdagi natija oynasida manzil satri yo'q. Shuning uchun oldingi darsdagidek misollarda xotiradagi router (createMemoryRouter) ishlatamiz. Haqiqiy loyihada — createBrowserRouter, qolgan kod bir xil.
import {
createMemoryRouter,
Link,
RouterProvider,
useParams,
} from "react-router";
const DISHES = {
osh: { name: "Osh", price: "35 000 so'm" },
lagmon: { name: "Lag'mon", price: "28 000 so'm" },
manti: { name: "Manti", price: "30 000 so'm" },
};
function Menu() {
return (
<ul>
{Object.entries(DISHES).map(([slug, dish]) => (
<li key={slug}>
<Link to={`/taom/${slug}`}>{dish.name}</Link>
</li>
))}
</ul>
);
}
function Dish() {
const { slug } = useParams();
const dish = DISHES[slug];
return (
<section>
<h2>{dish.name}</h2>
<p>Narxi: {dish.price}</p>
<Link to="/">Menyuga qaytish</Link>
</section>
);
}
const router = createMemoryRouter([
{ path: "/", element: <Menu /> },
{ path: "/taom/:slug", element: <Dish /> },
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda («Manti» bosilgach):
Manti
Narxi: 30 000 so'm
Menyuga qaytish«Ishga tushir» ni bosing va uchala taomni ham ochib ko'ring. Dish — bitta komponent, marshrut ham bitta. Menyuga to'rtinchi taom qo'shsangiz, DISHES ga bitta qator yozasiz, xolos — marshrutga tegmaysiz.
Qatorma-qator nima bo'ldi:
| Qadam | Manzil | useParams() |
Ekranda |
|---|---|---|---|
| ochilish | / |
{} |
menyu |
| «Manti» bosildi | /taom/manti |
{ slug: "manti" } |
Manti, 30 000 so'm |
| «Menyuga qaytish» | / |
{} |
menyu |
Slug — manzilda ishlatiladigan qisqa, o'qiladigan nom: lagmon, kok-choy. Raqam (/taom/12) o'rniga slug ishlatilsa, havola odamga ham, Google'ga ham tushunarli bo'ladi. Nomlash sizning qo'lingizda: :slug, :id, :taomId — useParams obyektidagi kalit aynan shu nom bo'ladi.
2.2 Parametr doim satr
«Bahor» da buyurtmalar raqamlangan: /buyurtma/12. Raqam bo'lgani uchun Sardor uni son deb o'yladi va === 12 bilan solishtirdi. Natija — "topilmadi". Sababini konsol ko'rsatadi. useLocation() — joriy manzilni beradigan hook, uni keyingi darsda to'liq ochamiz. Hozir u faqat "manzil satri" o'rnida ekranga yozadi:
import {
createMemoryRouter,
Link,
RouterProvider,
useLocation,
useParams,
} from "react-router";
const ORDERS = [
{ id: 12, table: 4, total: "63 000 so'm" },
{ id: 13, table: 7, total: "35 000 so'm" },
];
function Address() {
const { pathname } = useLocation();
return <p>Manzil: {pathname}</p>;
}
function Order() {
const { id } = useParams();
console.log(typeof id, id);
const number = Number(id);
const order = Number.isInteger(number)
? ORDERS.find((o) => o.id === number)
: undefined;
return (
<section>
<Address />
{order === undefined ? (
<p>«{id}» raqamli buyurtma yo'q.</p>
) : (
<p>
{order.table}-stol, jami {order.total}
</p>
)}
<Link to="/buyurtma/abc">Noto'g'ri havola</Link>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/buyurtma/:id", element: <Order /> }],
{ initialEntries: ["/buyurtma/12"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
string 12
string abcEkranda (havola bosilgach):
Manzil: /buyurtma/abc
«abc» raqamli buyurtma yo'q.
Noto'g'ri havolainitialEntries — xotiradagi router qaysi manzildan boshlashi. Birinchi chizishda manzil /buyurtma/12 edi va ekranda "4-stol, jami 63 000 so'm" chiqdi. Konsolning birinchi qatoriga qarang: string 12. Manzil — matn, router undan son yasamaydi. Shuning uchun:
Number(id)bilan songa aylantiramiz.Number.isIntegerbilan tekshiramiz:"abc"danNaNchiqadi, u butun son emas.- Topilmasa — yiqilmaymiz, odamga tushunarli xabar chiqaramiz.
Uchinchi qadam muhim: eski havola, xato nusxalangan havola, o'chirilgan taom — hammasi :id ga tushadi. Birinchi misolda /taom/shorva ochilsa, dish.name — xato va oq ekran. Dinamik marshrutda "topilmadi" holati doim bo'lsin.
Diqqat:
useParams()dagi har qiymat —stringyokiundefined. TypeScript ham shunday deydi:Readonly<Params<string>>, ya'nistring | undefined. Son, sana yoki boshqa tur kerak bo'lsa — o'zingiz aylantirasiz va tekshirasiz.
2.3 Bir nechta parametr
Bitta yo'lda bir nechta dinamik segment bo'lishi mumkin — nomlari har xil bo'lsin:
{ path: "/bolim/:category/taom/:slug", element: <Dish /> }
// /bolim/issiq/taom/osh → useParams():
// { category: "issiq", slug: "osh" }Segment faqat ikki / orasidagi bo'lakni oladi: :slug hech qachon / ni o'z ichiga olmaydi. /bolim/issiq/taom/osh/eski bu marshrutga mos kelmaydi — oxirida ortiqcha segment bor.
2.4 Tuzoq: parametr o'zgarsa, state qoladi
Taom sahifasiga «Porsiya» hisoblagichi qo'shdik. Mehmon oshdan 3 porsiya tanladi, keyin «Manti» ga o'tdi:
import { useState } from "react";
import {
createMemoryRouter,
Link,
RouterProvider,
useParams,
} from "react-router";
function Dish() {
const { slug } = useParams();
const [portions, setPortions] = useState(1);
return (
<section>
<h2>{slug}</h2>
<p>
Porsiya: {portions}{" "}
<button onClick={() => setPortions(portions + 1)}>+</button>
</p>
<Link to="/taom/manti">Manti</Link>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/taom/:slug", element: <Dish /> }],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
manti
Porsiya: 3 +
MantiManti sahifasi ochildi, lekin porsiya — 3! Sabab — State'ning saqlanishi va key bilan reset darsidagi qoida: daraxtning o'sha joyida o'sha Dish turibdi, faqat parametr o'zgardi. React uni qayta o'rnatmaydi, state saqlanadi.
Davosi o'sha darsdagidek — key. Parametrni kalit qilsangiz, har taom — alohida nusxa:
import { useState } from "react";
import {
createMemoryRouter,
Link,
RouterProvider,
useParams,
} from "react-router";
function Dish() {
const { slug } = useParams();
return <DishCard key={slug} slug={slug} />;
}
function DishCard({ slug }) {
const [portions, setPortions] = useState(1);
return (
<section>
<h2>{slug}</h2>
<p>
Porsiya: {portions}{" "}
<button onClick={() => setPortions(portions + 1)}>+</button>
</p>
<Link to="/taom/manti">Manti</Link>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/taom/:slug", element: <Dish /> }],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
manti
Porsiya: 1 +
Mantislug o'zgardi — key o'zgardi — React eski DishCard ni olib tashlab, yangisini 1 porsiya bilan qo'ydi. Qoida: dinamik sahifada "shu yozuvga tegishli" state bo'lsa (forma qoralamasi, tanlov, ochiq panel), unga parametrni key qilib bering.
Tekshirib ko'ring:
/buyurtma/012ochilsa, birinchi misoldagiOrdernima ko'rsatadi?useParams()dagiidnima bo'ladi?
Javob
id — "012" (satr, manzildagidek). Number("012") — 12, demak 12-buyurtma topiladi: "4-stol, jami 63 000 so'm". Ya'ni bitta buyurtmaning endi ikki manzili bor: /buyurtma/12 va /buyurtma/012. Bu SEO va havola ulashishda chalkashlik beradi. Keyingi darsda <Navigate replace> bilan "bir yozuv — bitta manzil" qilamiz — vazifalar da /vazifa/02 → /vazifa/2.
3. Qaysi marshrut yutadi: statik, dinamik, ixtiyoriy va *
3.1 Statik segment dinamikdan kuchli
Jasur aka xodimlar uchun "yangi taom qo'shish" sahifasini xohladi: /taom/yangi. Lekin /taom/:slug ham yangi ni qabul qiladi! Router qaysi birini tanlaydi? Sinab ko'ramiz. Ataylab dinamik marshrutni oldin yozdik:
import {
createMemoryRouter,
Link,
RouterProvider,
useParams,
} from "react-router";
function Dish() {
const { slug } = useParams();
return <p>Taom sahifasi: {slug}</p>;
}
function NewDish() {
return <p>Yangi taom qo'shish formasi</p>;
}
function Menu() {
return (
<p>
<Link to="/taom/osh">Osh</Link>{" "}
<Link to="/taom/yangi">Yangi taom</Link>
</p>
);
}
const router = createMemoryRouter([
{ path: "/", element: <Menu /> },
{ path: "/taom/:slug", element: <Dish /> },
{ path: "/taom/yangi", element: <NewDish /> },
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Yangi taom qo'shish formasiRouter marshrutlarni yozilgan tartibda sinamaydi — har biriga ball berib, eng aniq mosini tanlaydi:
| Segment turi | Misol | Kuchi |
|---|---|---|
| statik | yangi |
eng kuchli |
| dinamik | :slug |
o'rtacha |
| hammasini ushlovchi | * |
eng kuchsiz |
Tartib haqida o'ylamaysiz. Lekin endi yangi slug'li taom sahifasi hech qachon ochilmaydi — slug yaratishda band so'zlarni (yangi, tahrir) taqiqlang.
Maslahat: Eski maqolada
<Switch>va "aniq marshrutni yuqoriga yozing" maslahatini ko'rsangiz — bu React Router 5 davri ("birinchi mos kelgani yutadi"). 6–8 versiyalarda tartib ahamiyatsiz.
3.2 Ixtiyoriy segment: ?
«Bahor» menyusi ikki tilda: /menyu (o'zbekcha) va /menyu/en (inglizcha). Ikki marshrut yozish shart emas. Segment oxiriga ? qo'yilsa, u ixtiyoriy (optional) bo'ladi: bo'lsa ham, bo'lmasa ham mos keladi.
import {
createMemoryRouter,
Link,
RouterProvider,
useParams,
} from "react-router";
const TITLES = { uz: "Bugungi menyu", en: "Today's menu" };
function Menu() {
const { lang = "uz" } = useParams();
console.log("lang:", lang);
return (
<section>
<h2>{TITLES[lang] ?? TITLES.uz}</h2>
<Link to="/menyu">O'zbekcha</Link>{" "}
<Link to="/menyu/en">English</Link>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu/:lang?", element: <Menu /> }],
{ initialEntries: ["/menyu"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
lang: uz
lang: enEkranda:
Today's menu
O'zbekcha English/menyu da useParams() — {}, ya'ni lang umuman yo'q. Destrukturlashdagi = "uz" (Obyekt destructuring) aynan shu holat uchun. Statik segment ham ixtiyoriy bo'lishi mumkin: /menyu/bugun? — /menyu va /menyu/bugun ikkalasi ham.
3.3 * — hammasini ushlovchi
Hammasini ushlovchi (catch-all, splat) marshrut — path: "*". U boshqa hech biriga mos kelmagan har qanday manzilni oladi. Oldingi darsda vazifalar dagi 404 sahifasi aynan shunday qurilgan edi. Yulduzcha yo'lning oxirida ham bo'lishi mumkin: /fayl/* — /fayl/ bilan boshlangan hamma manzil. Qolgan qism useParams()["*"] da:
import {
createMemoryRouter,
RouterProvider,
useParams,
} from "react-router";
function File() {
const params = useParams();
return <p>Fayl yo'li: {params["*"]}</p>;
}
function NotFound() {
return <p>Bunday sahifa yo'q</p>;
}
const router = createMemoryRouter(
[
{ path: "/fayl/*", element: <File /> },
{ path: "*", element: <NotFound /> },
],
{ initialEntries: ["/fayl/rasmlar/osh.webp"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Fayl yo'li: rasmlar/osh.webpDinamik segmentdan farqi: * bir nechta segmentni, / lari bilan birga oladi. Fayl yo'li, hujjat bo'limlari (/qollanma/boshlash/ornatish) kabi chuqurligi noma'lum manzillar uchun qulay.
* umuman bo'lmasa-chi? Manzil hech biriga mos kelmasa, router o'zining standart xato sahifasini chiqaradi:
import { createMemoryRouter, RouterProvider } from "react-router";
const router = createMemoryRouter(
[{ path: "/", element: <p>Bosh sahifa</p> }],
{ initialEntries: ["/eski-havola"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
Error handled by React Router default ErrorBoundary: ErrorResponseImpl {status: 404, statusText: 'Not Found', data: 'Error: No route matches URL "/eski-havola"', error: Error: No route matches URL "/eski-havola", internal: true}Ekranda:
Unexpected Application Error!
404 Not Found
💿 Hey developer 👋
You can provide a way better UX than this when your app throws errors by providing your own ErrorBoundary or errorElement prop on your route." Hey developer" qatorlari — dev rejimidagi maslahat: "o'z ErrorBoundary yoki errorElement ingizni bering". Production build'da faqat birinchi ikki qator qoladi. Mehmon uchun bu inglizcha, qo'rqitadigan matn. Xato sahifasi (errorElement) — React Router IV: data mode darsida. 404 uchun esa * marshruti yetarli: u xato emas, oddiy sahifa.
Tekshirib ko'ring: Marshrutlar:
/taom/:slug,/taom/yangi,*./taom/yangi/rasmmanzili qaysi biriga tushadi?
Javob
* ga. /taom/yangi — aniq ikki segment, /taom/:slug ham ikki segment. Uchinchi segment (rasm) ularning hech biriga sig'maydi. Dinamik segment / dan o'tolmaydi. Qolgan yagona nomzod — *, u har qanday manzilni oladi.
4. Ichma-ich marshrutlar va <Outlet />
4.1 Bo'lim almashadi, sarlavha qoladi
Endi ikkinchi talab: /taom/osh (tarkibi) va /taom/osh/sharhlar. Sarlavha, narx va ikki havola ikkalasida bir xil.
Oldingi darsda children va <Outlet /> ni bir qavatda — vazifalar qobig'ida qisqa ko'rdik. Endi chuqurroq. Ichma-ich marshrut (nested route) — boshqa marshrutning children massivi ichidagi marshrut. Ota umumiy qismni chizadi, bola esa otaning <Outlet /> turgan joyida chiqadi — ramka ichidagi rasm o'rni. Endi ota o'zi ham dinamik (:slug):
import { useEffect } from "react";
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useParams,
} from "react-router";
const DISHES = {
osh: {
name: "Osh",
price: "35 000 so'm",
parts: "guruch, sabzi, go'sht",
reviews: 2,
},
};
function Dish() {
const { slug } = useParams();
const dish = DISHES[slug];
useEffect(() => console.log("Dish ekranga chiqdi"), []);
return (
<section>
<h2>
{dish.name} — {dish.price}
</h2>
<Link to={`/taom/${slug}`}>Tarkibi</Link>{" "}
<Link to={`/taom/${slug}/sharhlar`}>Sharhlar</Link>
<Outlet />
</section>
);
}
function Parts() {
const { slug } = useParams();
return <p>Tarkibi: {DISHES[slug].parts}</p>;
}
function Reviews() {
const { slug } = useParams();
return <p>Sharhlar: {DISHES[slug].reviews} ta</p>;
}
const router = createMemoryRouter(
[
{
path: "/taom/:slug",
element: <Dish />,
children: [
{ index: true, element: <Parts /> },
{ path: "sharhlar", element: <Reviews /> },
],
},
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
Dish ekranga chiqdiEkranda («Sharhlar» bosilgach):
Osh — 35 000 so'm
Tarkibi Sharhlar
Sharhlar: 2 taUch narsaga e'tibor bering:
- Bola yo'li nisbiy.
path: "sharhlar"—/siz. Router uni otaning yo'liga qo'shadi:/taom/:slug+sharhlar=/taom/:slug/sharhlar. - Bola ham parametrni ko'radi.
Reviewsning o'z yo'lida:slugyo'q, lekinuseParams()uni beradi. Parametrlar butun mos kelgan zanjir bo'ylab yig'iladi. - Ota qayta o'rnatilmadi. «Sharhlar» bosilganda konsolga yangi qator chiqmadi.
Dishjoyida qoldi, faqatOutletichi almashdi. Ota state'i (masalan, porsiya) bo'lim almashganda yo'qolmaydi.
Marshrut daraxti va ekrandagi tuzilma bir-biriga mos. Chapda — marshrutlar, o'ngda — /taom/osh/sharhlar ochilganda ekranda nima ichida nima turishi:
flowchart LR
subgraph M["Marshrutlar"]
R1["/taom/:slug"] --> R2["index"]
R1 --> R3["sharhlar"]
end
subgraph E["Ekranda: /taom/osh/sharhlar"]
D["Dish: sarlavha, havolalar"] --> O["Outlet"]
O --> S["Reviews"]
end
R3 -. "mos keldi" .-> S4.2 Index marshrut — otaning "o'z" sahifasi
{ index: true, element: <Parts /> } — index marshrut (oldingi darsdagi ro'yxat sahifasi kabi). Yo'li yo'q: ota manzili aynan ochilganda (/taom/osh) Outlet ga tushadi. Index bo'lmasa, /taom/osh da Outlet bo'sh qoladi. Index marshrutda children bo'lmaydi — u daraxtning "bargi".
Diqqat: Bola yo'lini
/bilan boshlamang.path: "/sharhlar"— mutlaq yo'l, router uni/taom/:slugichida qabul qilmaydi va ilova umuman ishga tushmaydi. Xato matni «Ko'p uchraydigan xatolar» bo'limida.
4.3 Nechta qavat bo'lsa ham
Qavatlar ko'p bo'lishi mumkin: /taom/osh/sharhlar/7 → mos kelgan zanjir [qobiq, taom, sharhlar, sharh]. Har biri keyingisini o'z Outlet iga chizadi — matryoshka kabi.
Tekshirib ko'ring: Ichma-ich misolda
Dishdagi<Outlet />ni o'chirib tashlasak,/taom/osh/sharhlarochilganda ekranda nima bo'ladi?
Javob
Faqat sarlavha va ikki havola. Router Reviews ni topdi, lekin chizadigan joy yo'q — xato ham, ogohlantirish ham chiqmaydi. "Bola ko'rinmayapti" — avval otada Outlet borligini tekshiring.
5. Layout marshrutlar
5.1 Ramka — butun ilova uchun
Sayt menyusi, "manzil satri" — hamma sahifada bir xil. Ularni bitta ota marshrutga chiqaramiz. Bu — oldingi darsdagi layout marshrut, vazifalar dagi Qobiq kabi: path: "/", menyu va <Outlet />, bolalari — index, boshqa sahifalar va *. Endi uni taom sahifasi bilan birga ko'ramiz:
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useLocation,
useParams,
} from "react-router";
function Shell() {
const { pathname } = useLocation();
return (
<div>
<p>
«Bahor» | <Link to="/">Menyu</Link>{" "}
<Link to="/ichimliklar">Ichimliklar</Link>
</p>
<p>Manzil: {pathname}</p>
<Outlet />
</div>
);
}
function Menu() {
return <Link to="/taom/osh">Osh</Link>;
}
function Dish() {
return <p>Taom: {useParams().slug}</p>;
}
const router = createMemoryRouter([
{
path: "/",
element: <Shell />,
children: [
{ index: true, element: <Menu /> },
{ path: "taom/:slug", element: <Dish /> },
{
path: "ichimliklar",
element: <p>Ko'k choy — 5 000 so'm</p>,
},
{ path: "*", element: <p>Bunday sahifa yo'q</p> },
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
«Bahor» | Menyu Ichimliklar
Manzil: /ichimliklar
Ko'k choy — 5 000 so'mShell bir marta yozildi va har sahifada bor — * (404) da ham: mehmon adashsa, menyu ko'rinib turadi.
5.2 Yo'lsiz layout — ramka bor, segment yo'q
Endi Jasur aka menyu va ichimliklar sahifalariga "Bugun 10% chegirma" bannerini qo'ymoqchi. Aloqa sahifasiga — yo'q. Manzillar o'zgarmasin: /menyu, /ichimliklar, /aloqa.
Banner uchun ota kerak. Lekin otaga yo'l bersak (/aksiya/menyu), manzillar o'zgaradi. Yechim — yo'lsiz layout marshrut (pathless layout route): element va children bor, path yo'q. U daraxtga qavat qo'shadi, manzilga esa hech narsa qo'shmaydi.
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
} from "react-router";
function Shell() {
return (
<div>
<p>
<Link to="/menyu">Menyu</Link>{" "}
<Link to="/ichimliklar">Ichimliklar</Link>{" "}
<Link to="/aloqa">Aloqa</Link>
</p>
<Outlet />
</div>
);
}
function SaleBanner() {
return (
<section>
<p>Bugun 10% chegirma!</p>
<Outlet />
</section>
);
}
const router = createMemoryRouter(
[
{
path: "/",
element: <Shell />,
children: [
{
element: <SaleBanner />,
children: [
{ path: "menyu", element: <p>Osh, Manti</p> },
{ path: "ichimliklar", element: <p>Ko'k choy</p> },
],
},
{ path: "aloqa", element: <p>+998 90 000 00 00</p> },
],
},
],
{ initialEntries: ["/aloqa"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Menyu Ichimliklar Aloqa
Bugun 10% chegirma!
Ko'k choy/aloqa da banner yo'q edi, /ichimliklar da — bor, manzil esa shunchaki /ichimliklar. Yo'lsiz layout'ning ikki ishi:
- umumiy ko'rinish — bir guruh sahifaga banner, yon panel yoki boshqa ramka;
- umumiy shart — "bu sahifalarga faqat kirgan foydalanuvchi". Himoyalangan marshrutlar darsida
vazifalardagiHimoyalanganaynan yo'lsiz layout bo'ladi.
| Marshrut | path |
element |
Nima beradi |
|---|---|---|---|
| oddiy sahifa | bor | bor | bitta sahifa |
| layout (yo'lli) | bor | bor + Outlet |
ramka va manzil qismi |
| layout (yo'lsiz) | yo'q | bor + Outlet |
faqat ramka |
| index | yo'q (index: true) |
bor | otaning o'z sahifasi |
5.3 <Outlet context> — bolalarga ma'lumot uzatish
Ichma-ich misolda Parts va Reviews taomni qayta qidirdi: DISHES[slug]. Ota uni allaqachon topgan edi. Bolalar otadan to'g'ridan-to'g'ri olsa-chi? Props berib bo'lmaydi — <Outlet /> qaysi bola chizilishini o'zi hal qiladi.
Buning uchun Outlet da context prop'i bor. Bola uni useOutletContext() bilan o'qiydi. Bu — Context I darsidagi g'oyaning kichik varianti, faqat bitta qavat uchun.
Bitta nozik joy bor. Ichma-ich ikki layout bo'lsa, ichkisining konteksti tashqisinikini almashtiradi, birlashtirmaydi:
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useOutletContext,
} from "react-router";
function Shell() {
return <Outlet context={{ cafe: "Bahor" }} />;
}
function Dish() {
const outer = useOutletContext();
console.log("Dish oldi:", outer);
return (
<section>
<Link to="/taom/osh/sharhlar">Sharhlar</Link>
<Outlet context={{ dish: "Osh" }} />
</section>
);
}
function Reviews() {
const inner = useOutletContext();
console.log("Reviews oldi:", inner);
return <p>{inner.dish} sharhlari ({inner.cafe})</p>;
}
const router = createMemoryRouter(
[
{
path: "/",
element: <Shell />,
children: [
{
path: "taom/:slug",
element: <Dish />,
children: [{ path: "sharhlar", element: <Reviews /> }],
},
],
},
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
Dish oldi: {cafe: 'Bahor'}
Reviews oldi: {dish: 'Osh'}Ekranda:
Sharhlar
Osh sharhlari ()Reviews faqat {dish: 'Osh'} ni oldi — cafe yo'qoldi, qavslar bo'sh. useOutletContext() eng yaqin ota Outlet ining kontekstini beradi. Bobosinikiga yetib bormaydi. Ikkalasi ham kerak bo'lsa, ota ularni o'zi birlashtiradi: context={{ ...outer, dish: "Osh" }}. Bugungi vazifalar qadamida aynan shu qilinadi.
Maslahat:
useOutletContext()turini bilmaydi — TypeScript'daunknown. Kanonda shuning uchun kichik o'rovchi hook yoziladi:useQobiq()vauseVazifa(). Ular to'g'ri turni qaytaradi va har sahifadauseOutletContext<…>()deb yozish shart emas.
Tekshirib ko'ring: Yo'lsiz layout misolida
/menyuochilsa,SaleBannernecha marta ekranga chiqadi va manzil qanday bo'ladi?
Javob
Bir marta: zanjir — Shell → SaleBanner → "Osh, Manti". Manzil — /menyu. Yo'lsiz qavat manzilga hech narsa qo'shmaydi, chunki uning path i yo'q. Shuning uchun undagi bolalar yo'li to'g'ridan-to'g'ri Shell yo'liga (/) qo'shiladi.
6. Vazifalar qadami: vazifa sahifasi (holat 03)
6.1 Nima quramiz
vazifalar ro'yxatidagi har qatorga «Batafsil» havolasi. U /vazifa/2 kabi sahifani ochadi: sarlavhada vazifa matni, pastda holati (faol yoki bajarilgan), teglari va raqami. Sahifa o'zi layout: hozircha ichida bitta bo'lim — index. Keyingi qadamlarda unga tahrir (/vazifa/2/tahrir) qo'shiladi, shuning uchun Outlet hozirdan bor.
git switch -c feat/vazifa-sahifasi6.2 Marshrut: marshrutlar.tsx
Qobiq ning bolalariga bitta yozuv (kanon):
+import { VazifaSahifasi } from "./sahifalar/VazifaSahifasi.tsx";
+import { VazifaUmumiy } from "./sahifalar/VazifaUmumiy.tsx";
...
{ path: "sozlamalar", element: <SozlamalarSahifasi /> },
+ // 19/#03: dinamik segment — ":id" har qanday qiymat
+ // (/vazifa/2, /vazifa/abc); ichida — o'z bolalari
+ // (ichma-ich)
+ {
+ path: "vazifa/:id",
+ element: <VazifaSahifasi />,
+ children: [{ index: true, element: <VazifaUmumiy /> }],
+ },
// "*" — boshqa hech biriga mos kelmagan manzil
{ path: "*", element: <TopilmadiSahifasi /> },Bugungi darsning uch g'oyasi bir yozuvda: dinamik segment (:id), ichma-ich marshrut (children) va index bola. Yo'l nisbiy — Qobiq ning / iga qo'shiladi.
6.3 Sahifa: VazifaSahifasi.tsx
Kod kanondan aynan, faqat izohlar telefonga sig'ishi uchun qisqa qatorlarga bo'lingan:
// VazifaSahifasi.tsx — "/vazifa/:id" (19/#03): bitta vazifa.
// Dinamik marshrut — :id manzildan olinadi (useParams), har
// vazifaga alohida komponent yozilmaydi. O'zi ham layout: ichidagi
// bo'limlar (ichma-ich marshrutlar) <Outlet /> o'rnida, vazifa
// ularga kontekst bilan beriladi
import { Link, Outlet, useParams } from "react-router";
import { boshlangichVazifalar } from "../holat.ts";
import { yukla } from "../saqlash.ts";
import { useQobiq } from "./qobiq-konteksti.ts";
import type { VazifaKonteksti } from "./vazifa-konteksti.ts";
export function VazifaSahifasi() {
const { sarlavhaRef } = useQobiq();
// Manzil parametri — doim satr (yoki undefined): "/vazifa/abc"
// ham shu marshrutga tushadi, son ekanini o'zimiz tekshiramiz
const { id } = useParams();
const raqam = Number(id);
// Ro'yxat — shu brauzerdagi nusxadan (19/#05 da loader
// serverdan oladi)
const vazifa = Number.isInteger(raqam)
? (yukla() ?? boshlangichVazifalar).find((v) => v.id === raqam)
: undefined;
if (vazifa === undefined) {
return (
<>
<title>Vazifa topilmadi — Vazifalar</title>
<h2 ref={sarlavhaRef} tabIndex={-1}>
Vazifa topilmadi
</h2>
<p id="vazifa-yoq">{`«${id ?? ""}» raqamli vazifa yo'q.`}</p>
<p>
<Link to="/">Ro'yxatga qaytish</Link>
</p>
</>
);
}
const kontekst: VazifaKonteksti = { sarlavhaRef, vazifa };
return (
<article id="vazifa" aria-labelledby="vazifa-sarlavha">
<title>{`${vazifa.matn} — Vazifalar`}</title>
<h2 id="vazifa-sarlavha" ref={sarlavhaRef} tabIndex={-1}>
{vazifa.matn}
</h2>
<Outlet context={kontekst} />
<p>
<Link to="/">Ro'yxatga qaytish</Link>
</p>
</article>
);
}Bugun o'rganganingiz qatorma-qator:
useParams()→id— satr.Number(id)vaNumber.isInteger— «Parametr doim satr» bo'limidagi uch qadamning o'zi.- Topilmasa — yiqilmaydi: "Vazifa topilmadi" va ro'yxatga havola.
/vazifa/99ham,/vazifa/abcham shu yerga tushadi. - Ro'yxat hozircha brauzerdagi nusxadan (
yukla()). Serverdan olish — data mode darsida, loader bilan. <title>— React 19 uni hujjat sarlavhasiga ko'chiradi.sarlavhaRef—Qobiqdan (skip link uchun); bolalarga ham kerak, shuning uchun kontekstda qayta uzatiladi.
6.4 Kontekst: vazifa-konteksti.ts
Oxirgi nuqta — «<Outlet context>» bo'limidagi tuzoq. VazifaSahifasi ning Outlet konteksti Qobiq nikini almashtiradi. Agar unda faqat { vazifa } bo'lsa, bolalar sarlavhaRef ni yo'qotardi. Shuning uchun tur ikkalasining birlashmasi (kanon):
// vazifa-konteksti.ts — VazifaSahifasi o'z bo'limlariga beradigan
// narsa (19/#03): Qobiq kontekstiga qo'shimcha — shu sahifadagi
// vazifa. Ichma-ich Outlet konteksti ota kontekstni almashtiradi
// (birlashtirmaydi) — shuning uchun sarlavhaRef ham shu yerda
import { useOutletContext } from "react-router";
import type { VazifaMalumoti } from "../turlar.ts";
import type { QobiqKonteksti } from "./qobiq-konteksti.ts";
export type VazifaKonteksti = QobiqKonteksti & {
vazifa: VazifaMalumoti;
};
export function useVazifa(): VazifaKonteksti {
return useOutletContext<VazifaKonteksti>();
}QobiqKonteksti & { vazifa } — Kesishma darsidagi kesishma (intersection) turi: ikkala turning hamma maydonlari. Index bola — VazifaUmumiy.tsx — vazifani useVazifa() dan oladi va qayta qidirmaydi:
export function VazifaUmumiy() {
const { vazifa } = useVazifa();
const teglar = [...teglariniOl(vazifa.matn)];
return (
<dl className="vazifa-malumoti">
<dt>Holati</dt>
<dd id="vazifa-holati">
{vazifa.bajarildi ? "Bajarilgan" : "Faol"}
</dd>
<dt>Teglar</dt>
<dd id="vazifa-teglari">
{teglar.length === 0 ? "yo'q" : teglar.join(", ")}
</dd>
<dt>Raqami</dt>
<dd>{vazifa.id}</dd>
</dl>
);
}teglariniOl — ro'yxatdagi teglarni ajratadigan funksiya (royxat.ts, Suspense darsida eksport qilingan). <dl> — "atama — qiymat" juftliklari uchun HTML ro'yxati, ekran o'quvchi ham shunday o'qiydi.
6.5 Qatorda havola: OddiyQator.tsx
Har qator oxiriga <Link> (kanon):
+import { Link } from "react-router";
...
onClick={() => onOchir(vazifa.id)}
/>
+ {/* 19/#03: vazifa sahifasi. Link — sahifani qayta
+ yuklamaydi (router manzilni o'zgartiradi); qatorning
+ oxirida — Tab tartibi (katak, tahrir, o'chirish)
+ o'zgarmaydi */}
+ <Link
+ to={`/vazifa/${vazifa.id}`}
+ className="batafsil"
+ aria-label={`Batafsil: ${vazifa.matn}`}
+ >
+ Batafsil
+ </Link>
</Qator>Nega qator oxirida? Bobur (faqat klaviatura) qatorlar bo'ylab Tab bilan yuradi: katak, tahrir, o'chirish. Havola o'rtaga qo'yilsa, uning o'rgangan tartibi buziladi. aria-label — Malika (ekran o'quvchi) uchun: o'nta "Batafsil" ni bir-biridan ajratib bo'lmaydi, "Batafsil: Non olish #bozor" esa aniq.
vazifalar.css ga 12 qator: havola rangi va joyi, .vazifa-malumoti — ikki ustunli grid.
6.6 Testlar va tekshiruvlar
Qatorda endi <Link> bor, u router'siz ishlamaydi — Vitest testlari komponentni xotiradagi router ichida chizadi (kanon, VazifaRoyxati.test.tsx):
+// 19/#03: qatorda <Link> — router konteksti kerak (xotiradagi
+// router)
+import { MemoryRouter } from "react-router";
...
- );
+ { wrapper: MemoryRouter },
+ );MemoryRouter — oldingi darsdagi declarative mode'dagi xotira router'i. Yangi tekshiruv havola manzilini solishtiradi: getAttribute("href") — "/vazifa/1". Testlar chuqur — React Testing Library I darsida.
git add -A
git commitCommit (kanon): feat: /vazifa/:id — vazifa sahifasi, qatorda «Batafsil».
Tekshiruvlar (haqiqiy, holat 03):
npm test— node:testℹ tests 174 · ℹ pass 174, VitestTests 49 passed (49). Son o'zgarmadi: mavjud testlar router ichiga olindi, yangi tekshiruv mavjud testga qo'shildi.lint,format:check,tip,knip—exit 0. Build:index-….js 359.02 kB │ gzip: 113.68 kB(oldin 113.36 — sahifa +0.3 KB). Router allaqachon to'plamda, yangi sahifa deyarli bepul.- Brauzer: 13–17-qism tekshiruvlari 159/159, 18-qismniki 72/72, 19-qismniki 26/26. Yangi yettitasi —
/vazifa/:idhaqida:
✅ vazifa: «Batafsil» — sahifa (SPA), sarlavha, title: [true,"Non olish #bozor","Non olish #bozor — Vazifalar"]
✅ vazifa: holati va teglari (index bo'limi): ["Faol","#bozor","1"]
✅ vazifa: menyuda hech biri joriy emas: 0
✅ vazifa: to'g'ridan ochildi (/vazifa/2) — Bajarilgan: ["Sut olish #bozor","Bajarilgan"]
✅ vazifa: /vazifa/99 — «Vazifa topilmadi»: ["Vazifa topilmadi","«99» raqamli vazifa yo'q."]
✅ vazifa: /vazifa/abc — «Vazifa topilmadi»: ["Vazifa topilmadi","«abc» raqamli vazifa yo'q."]
✅ vazifa: CSP buzilishi yo'q: []To'rtinchi qator: /vazifa/2 to'g'ridan ochildi — do'stga yuborilgan havola ham shunday ochiladi. Uchinchi qator: vazifa sahifasida menyudagi hech bir bo'lim "joriy" emas. Diff: 8 fayl, +139.
7. Ko'p uchraydigan xatolar
Eng ko'p uchraydigani — bola yo'lini / bilan boshlash. Ilova umuman ishga tushmaydi:
import {
createMemoryRouter,
Outlet,
RouterProvider,
} from "react-router";
const router = createMemoryRouter([
{
path: "/taom/:slug",
element: <Outlet />,
children: [{ path: "/sharhlar", element: <p>Sharhlar</p> }],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
Uncaught Error: Absolute route path "/sharhlar" nested under path "/taom/:slug" is not valid. An absolute child route path must start with the combined path of all its parent routes.Tarjimasi: "/taom/:slug ichidagi /sharhlar mutlaq yo'li noto'g'ri. Bolaning mutlaq yo'li barcha otalari yo'lining yig'indisi bilan boshlanishi kerak." Ya'ni ikki yo'l bor: path: "sharhlar" (nisbiy — tavsiya) yoki path: "/taom/:slug/sharhlar" (to'liq). Xato createMemoryRouter chaqirilgan joyda, marshrutlar o'qilayotganda chiqadi — komponent chizilishidan oldin.
| Belgi | Sabab | Davosi |
|---|---|---|
| bola sahifa ko'rinmaydi, xato yo'q | otada <Outlet /> yo'q |
ota elementiga <Outlet /> |
Cannot read properties of undefined (reading 'name') |
/taom/shorva — bunday taom yo'q, tekshiruv yo'q |
topilmadi holati: if (dish === undefined) |
=== 12 hech qachon rost emas |
parametr — satr "12" |
Number(id) + Number.isInteger |
| boshqa taomga o'tganda eski state qoladi | marshrut o'sha, komponent qayta o'rnatilmadi | key={slug} |
| "Unexpected Application Error! 404 Not Found" | * marshruti yo'q |
{ path: "*", element: <NotFound /> } |
useOutletContext() — undefined |
otaning Outlet ida context yo'q |
<Outlet context={…} /> |
| bola otaning kontekstidagi maydonni ko'rmaydi | ichki Outlet konteksti almashtirdi |
context={{ ...outer, yangi }} |
Cannot destructure property 'basename' … |
<Link> router'dan tashqarida (ko'pincha testda) |
wrapper: MemoryRouter (React Router I dagi xato) |
8. Mashqlar
Mashqlar kurs/mashqlar/19/03-marshrutlar/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi marshrut?
Marshrutlar (tartibi ataylab aralash):
- A —
/taom/:slug - B —
/taom/yangi - C —
/menyu/:lang? - D —
*
Har manzil qaysi marshrutga tushadi? Harfini yozing.
/taom/manti→/taom/yangi→/menyu→/menyu/en→/taom→/taom/osh/sharhlar→
Yechim
/taom/manti— A: ikki segment, ikkinchisi dinamikka tushadi./taom/yangi— B: statik segment dinamikdan kuchli, tartib ahamiyatsiz./menyu— C::lang?ixtiyoriy, bo'lmasa ham mos keladi./menyu/en— C:lang—"en"./taom— D: A va B ikki segment talab qiladi,:slugixtiyoriy emas./taom/osh/sharhlar— D: uchinchi segmentga hech kim joy bermagan.
2-mashq (o'rta): ikki sahifani ichma-ich qiling
Quyidagi kodda taom sarlavhasi ikki komponentda takrorlangan. Uni bitta ota Dish ga chiqaring:
/taom/:slug— ota,Dishsarlavha va<Outlet />ni chizadi.- Ota manzilining o'zida — narx (index marshrut).
/taom/:slug/sharhlar— sharhlar (nisbiy yo'l"sharhlar").
Ekran o'zgarmasligi kerak. Ishora: «Ichma-ich marshrutlar va <Outlet />» bo'limidagi misol.
import {
createMemoryRouter,
Link,
RouterProvider,
useParams,
} from "react-router";
function Title() {
const { slug } = useParams();
return (
<p>
{slug}: <Link to={`/taom/${slug}`}>Narxi</Link>{" "}
<Link to={`/taom/${slug}/sharhlar`}>Sharhlar</Link>
</p>
);
}
function Price() {
return (
<section>
<Title />
<p>35 000 so'm</p>
</section>
);
}
function Reviews() {
return (
<section>
<Title />
<p>2 ta sharh</p>
</section>
);
}
const router = createMemoryRouter(
[
{ path: "/taom/:slug", element: <Price /> },
{ path: "/taom/:slug/sharhlar", element: <Reviews /> },
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
osh: Narxi Sharhlar
2 ta sharhYechim
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useParams,
} from "react-router";
function Dish() {
const { slug } = useParams();
return (
<section>
<p>
{slug}: <Link to={`/taom/${slug}`}>Narxi</Link>{" "}
<Link to={`/taom/${slug}/sharhlar`}>Sharhlar</Link>
</p>
<Outlet />
</section>
);
}
const router = createMemoryRouter(
[
{
path: "/taom/:slug",
element: <Dish />,
children: [
{ index: true, element: <p>35 000 so'm</p> },
{ path: "sharhlar", element: <p>2 ta sharh</p> },
],
},
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
osh: Narxi Sharhlar
2 ta sharhTitle, Price va Reviews kerak bo'lmay qoldi: sarlavha bir joyda, bo'lim almashganda Dish (va uning state'i) joyida qoladi.
3-mashq (qiyin): taom topilmasa va kontekst
«Bahor» taom sahifasini yakunlang:
/taom/:slug—DishtaomniDISHESdan topadi. Topilmasa —<p>«…» degan taom yo'q.</p>(nuqtalar o'rnida slug) vaOutletumuman chizilmaydi.- Topilsa — sarlavha (
<h2>da nomi) va<Outlet context={{ dish }} />. - Index bola (
Price) narxniuseOutletContext()dan oladi:<p>Narxi: …</p>. Dishdan tashqarida*—<p>Bunday sahifa yo'q</p>.
DISHES — osh va manti (nom va narx). Router initialEntries: ["/taom/shorva", "/taom/manti", "/yoq"] bilan boshlansin — bu xotiradagi tarix, router sukut bo'yicha oxirgisidan (/yoq) boshlaydi. Har manzilni sinash uchun initialIndex: 0, 1, 2 qo'shing.
Yechim
import {
createMemoryRouter,
Outlet,
RouterProvider,
useOutletContext,
useParams,
} from "react-router";
const DISHES = {
osh: { name: "Osh", price: "35 000 so'm" },
manti: { name: "Manti", price: "30 000 so'm" },
};
function Dish() {
const { slug } = useParams();
const dish = DISHES[slug];
if (dish === undefined) {
return <p>«{slug}» degan taom yo'q.</p>;
}
return (
<section>
<h2>{dish.name}</h2>
<Outlet context={{ dish }} />
</section>
);
}
function Price() {
const { dish } = useOutletContext();
return <p>Narxi: {dish.price}</p>;
}
const router = createMemoryRouter(
[
{
path: "/taom/:slug",
element: <Dish />,
children: [{ index: true, element: <Price /> }],
},
{ path: "*", element: <p>Bunday sahifa yo'q</p> },
],
{
initialEntries: ["/taom/shorva", "/taom/manti", "/yoq"],
initialIndex: 1,
},
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda (initialIndex: 1 — /taom/manti):
Manti
Narxi: 30 000 so'mUchala manzil ham sinab ko'rildi:
initialIndex |
Manzil | Ekranda |
|---|---|---|
| 0 | /taom/shorva |
«shorva» degan taom yo'q. |
| 1 | /taom/manti |
Manti, Narxi: 30 000 so'm |
| 2 | /yoq |
Bunday sahifa yo'q |
/taom/shorva va /yoq farqiga qarang. Birinchisi marshrutga mos keldi (shakli to'g'ri), lekin bunday taom yo'q — buni faqat Dish biladi. Ikkinchisi hech qaysi marshrutga mos kelmadi — buni router biladi. Ikkalasiga ham "yo'q" sahifasi kerak, lekin ular har xil joyda tug'iladi. Price taomni qayta qidirmaydi: ota uni allaqachon topgan va tekshirgan.
4-mashq: Vazifalar qadami
feat/vazifa-sahifasi branch'ida «Vazifalar qadami» bo'limidagi o'zgarishlarni qiling: marshrutlar.tsx dagi yozuv, VazifaSahifasi.tsx, VazifaUmumiy.tsx, vazifa-konteksti.ts, OddiyQator.tsx dagi havola, CSS va ikki test faylidagi wrapper. Keyin commit.
Yechim
Tekshiruv ro'yxati:
npm test— node 174, Vitest 49.Linkxatosi bilan yiqilsa —wrapper: MemoryRouteryo'q.- «Batafsil» — sahifa qayta yuklanmaydi, sarlavha va tabda vazifa matni.
/vazifa/abcva/vazifa/99— "Vazifa topilmadi".- Qatorda Tab: katak, tahrir, o'chirish, keyin «Batafsil».
9. Real ishda
- Internet-do'konlar —
/product/123-qizil-ko'ylak: raqam bo'yicha qidiriladi, slug — SEO uchun. - Admin panellar —
/admin/users/:id/orders/:orderId: har qavatning o'z layout'i vaOutleti. - Next.js — xuddi shu g'oyalar papka bilan:
app/taom/[slug]/page.tsx— dinamik segment,layout.tsx— layout,(guruh)— yo'lsiz layout (Fayl-asosli routing, Layout va template). Bugun o'rganganlaringiz u yerga to'g'ridan-to'g'ri ko'chadi. - Ko'p uchraydigan bug — chatda boshqa suhbatga o'tganda yozilgan xabar qoladi:
key={id}— eng tez tuzatish. - Intervyu savollari: "nested route va
Outletqanday ishlaydi?", "useParamsnima qaytaradi va nega satr?", "layout route nima, yo'lsiz layout nimaga kerak?", "React Router qaysi marshrutni tanlashini qanday hal qiladi?".
Xulosa
:slug— dinamik segment: bitta marshrut ko'p sahifaga. Qiymat —useParams()dan, doim satr. Aylantiring, tekshiring, "topilmadi" holatini yozing.- Router eng aniq marshrutni tanlaydi: statik > dinamik >
*. Tartib ahamiyatsiz.?— ixtiyoriy segment. - Parametr o'zgarsa, komponent qayta o'rnatilmaydi — sahifaga tegishli state uchun
key={slug}. - Ichma-ich marshrut —
children: ota umumiy qismni chizadi, bola otaning<Outlet />ida chiqadi.index— ota manzilining o'zi. - Layout — ramka. Yo'lsiz layout manzilga segment qo'shmaydi, faqat ramka yoki umumiy shart beradi.
<Outlet context>→useOutletContext()— eng yaqin otanikini oladi. Ichkisi tashqisini almashtiradi, kerak bo'lsa birlashtiring.
Keyingi dars: React Router III: dasturiy navigatsiya va URL holati — useNavigate bilan koddan o'tish, replace, useSearchParams, useLocation va nisbiy yo'llar; vazifalar da «Orqaga» tugmasi.
Manbalar
- React Router: Routing (data mode) — reactrouter.com/start/data/routing (2026-10 holatiga, v8.4.0)
- React Router:
useParams,Outlet,useOutletContext— reactrouter.com/api/hooks/useParams, /api/components/Outlet, /api/hooks/useOutletContext - React Router: Route matching (ranking) — reactrouter.com/explanation (v8)
- React: state'ni saqlash va reset — react.dev/learn/preserving-and-resetting-state
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!