Nega bizga freymvork kerak
React interaktiv foydalanuvchi interfeyslarini yaratish uchun asosiy elementlarni taqdim etsa-da, u ko’plab muhim arxitektura qarorlarini dasturchilarga qoldiradi. React bu borada fikrga ega emas, bu esa dasturchilarga ilovalarini o’zlari xohlagan tarzda tuzish uchun moslashuvchanlik beradi. Biroq, ilovalar kengaygan sari, bu erkinlik ortiqcha yukka aylanishi mumkin. Siz doimiy ravishda takroriy muammolarni hal qilish, masalan, routing, ma’lumotlarni olish va serverda render qilish kabi muammolarni hal qilishda o’z-o’zidan qaytalanish xavfiga duch kelishingiz mumkin.
Bu yerda React freymvorklari yordamga keladi. Ular oldindan belgilangan strukturani va umumiy muammolar uchun yechimlarni taqdim etadi, bu esa dasturchilarga o’z ilovasining o’ziga xos tomonlariga e’tibor qaratishga imkon beradi, shuning uchun qaytariluvchan kod bilan shug’ullanish shart emas. Bu ishlab chiqish jarayonini sezilarli darajada tezlashtirishi va freymvork tomonidan ta’minlangan eng yaxshi amaliyotlarga rioya qilish orqali kod bazasining sifatini yaxshilashi mumkin.
Buni to’liq tushunish uchun, keling, o’zimizning minimal freymvorkimizni yozib ko’rishga harakat qilaylik. Buni amalga oshirish uchun, biz freymvorklardan osonlik bilan ololmaydigan bir nechta asosiy xususiyatlarni aniqlashimiz kerak. Qisqa qilib aytganda, biz freymvorklardan oladigan uchta asosiy xususiyatni aniqlaymiz. Freymvorklar ko’p narsalarni bajaradi, lekin bu kichik guruh bizni ajoyib muhokama qilish uchun asos yaratadi:
- Serverda render qilish
- Routing
- Ma’lumotlarni olish (Data fetching)
Sodda freymvorkimizni tuzish
Keling, mavjud bo’lgan tasavvuriy React ilovasini olib, bu xususiyatlarni bosqichma-bosqich qo’shamiz, shunda freymvorklar biz uchun nima qilishini tushunamiz. Biz “freymvork sifatida” qabul qilayotgan React ilovasining quyidagi strukturasi bor:
index.js
List.js
Detail.js
dist/
clientBundle.js
Mana, har bir fayl qanday ko’rinishga ega:
import React from "react";
import { createRoot } from "react-dom/client";
import Router from "./Router";
const root = createRoot(document);
const params = new URLSearchParams();
const thingId = params.get("id");
root.render(
window.location.pathname === "/" ? <List /> : <Detail thingId={thingId} />
);
export const List = () => {
const [things, setThings] = useState([]);
const [requestState, setRequestState] = useState("initial");
const [error, setError] = useState(null);
useEffect(() => {
setRequestState("loading");
fetch("https://api.com/get-list")
.then((r) => r.json())
.then(setThings)
.then(() => {
setRequestState("success");
})
.catch((e) => {
setRequestState("error");
setError(e);
});
}, []);
return (
<div>
<ul>
{things.map((thing) => (
<li key={thing.id}>{thing.label}</li>
))}
</ul>
</div>
);
};
export const Detail = ({ thingId }) => {
const [thing, setThing] = useState([]);
const [requestState, setRequestState] = useState("initial");
const [error, setError] = useState(null);
useEffect(() => {
setRequestState("loading");
fetch("https://api.com/get-thing/" + thingId)
.then((r) => r.json())
.then(setThing)
.then(() => {
setRequestState("success");
})
.catch((e) => {
setRequestState("error");
setError(e);
});
}, []);
return (
<div>
<h1>The thing!</h1>
{thing}
</div>
);
};
Duch keladigan ba’zi muammolar
Bu muammolar barcha faqat-klient tomonda render qilinadigan React ilovalariga ta’sir qiladi:
Foydalanuvchiga bo’sh sahifa jo’natamiz
Biz foydalanuvchiga faqat yuklash uchun kodni jo’natamiz, keyin esa JavaScript’ni tahlil qilib, bajarishimiz kerak.
JavaScript ishga tushguncha, foydalanuvchi bo’sh sahifani ko’radi va keyin ular bizning ilovamizni ko’radilar. Agar foydalanuvchi qidiruv tizimi bo’lsa, ular hech narsani ko’rmaydilar. Agar qidiruv tizimi boti JavaScript’ni qo’llab-quvvatlamasa, qidiruv tizimi bizning veb-saytimizni indekslamaydi.
Biz ma’lumotlarni juda kech olishni boshlaymiz
Ilovamiz foydalanuvchi bilan ishlash qulayligiga ta’sir etadigan network waterfall(tarmoq sharsharasi) deb ataladigan bir muammoga duch keladi: bu g’aroyib hodisa, tarmoq so’rovlarining ketma-ket amalga oshirilishi natijasida yuzaga keladi va ilovalarni sekinlashtiradi. Ilovamiz asosiy funksionallik uchun serverga bir nechta so’rovlar yuborishi kerak.
Masalan, ishga tushirish jarayoni quyidagicha amalga oshadi:
- JavaScript’ni yuklab olish, tahlil qilish va bajarish.
- React komponentlarini render qilish va tatbiq qilish.
useEffect
ma’lumotlarni yuklay boshlaydi.- Spinner’lar va boshqalarni render qilish va tatbiq qilish.
useEffect
ma’lumotlarni yuklashni tugatadi.- Ma’lumotlarni render qilish va tatbiq qilish. Bularning barchasini, agar biz brauzerga bevosita ma’lumotlar bilan sahifa jo’natadigan bo’lsak, oldini olishimiz mumkin: agar biz HTML markup’ini jo’natsak, xuddi 7-bobda server tomonidagi React’da muhokama qilinganidek.
Router’imiz faqat klient tomonida ishlashga asoslangan
Agar brauzer https://our-app.com/detail?thingId=24 manzilini so’rasa, server 404 sahifasini qaytaradi, chunki serverda bunday fayl yo’q. Bu muammoni hal qilish uchun ishlatiladigan keng tarqalgan hiyla — 404 topilganda HTML faylini render qilish va JavaScript yuklashini ta’minlaydigan sahifa yaratishdir va shunda klient tomonidagi router ishga tushadi. Bu hiyla qidiruv tizimlari yoki JavaScript qo’llab-quvvatlanishi cheklangan muhitlar uchun ishlamaydi.
Freymvorklar bu muammolarni va boshqalarni hal qilishda yordam beradi. Keling, ular buni qanday qilib amalga oshirayotganini o’rganamiz.
Server tomonda renderlash
Odatda, freymvorklar bizga serverda render qilish imkonini o’zi bilan taqdim qiladi. Ushbu ilovaga serverda render qilishni qo’shish uchun bizga server kerak. Biz buni Express.js kabi paket yordamida o’zimiz yozishimiz mumkin. Keyin ushbu serverni joylashtiramiz va ishlaymiz. Keling, bunday serverni boshqaradigan kodni ko’rib chiqamiz.
Diqqat qiling, bu yerda renderToString
funksiyasidan faqat soddalik uchun foydalanamiz va freymvorklar ushbu funksiyalarni qanday amalga oshirishini tushuntirish uchun qo’shdik. Haqiqiy foydalanish holatida, serverda render qilish uchun renderToPipeableStream
kabi kuchliroq asinxron API’lardan foydalanish afzal, bu 6-bobda batafsil yoritilgan.
Keling, buni amalga oshiramiz:
import express from "express";
import { renderToString } from "react-dom/server"; // 6-bobda yoritilgan
import { List } from "./List";
import { Detail } from "./Detail";
const app = express();
app.use(express.static("./dist")); // Statik fayllarni, masalan, klient JS’larini olish uchun
const createLayout = (children) => `<html lang="en">
<head>
<title>Mening sahifam</title>
</head>
<body>
${children}
<script src="/clientBundle.js"></script>
</body>
<html>`;
app.get("/", (req, res) => {
res.setHeader("Content-Type", "text/html");
res.end(createLayout(renderToString(<List />)));
});
app.get("/detail", (req, res) => {
res.setHeader("Content-Type", "text/html");
res.end(
createLayout(renderToString(<Detail thingId={req.params.thingId} />))
);
});
app.listen(3000, () => {
console.info("Ilova tinglayapti!");
});
Ushbu kod ilovamizga serverda render qilishni qo’shish uchun yetarli. E’tibor bering, klient tomoni uchun index.js
o’zining klient router’iga ega, va biz asosan server uchun yana bitta router qo’shdik. Freymvorklar izomorf router’lar birga keladi, ya’ni klient va serverda ishlaydigan router’lar.
Routing
Bu server yaxshi ishlayotgan bo’lsa-da, kengaytirishda qiyinchilik tug’diradi: har bir qo’shimcha router uchun o’zimiz req.get
chaqiruvlarini qo’shishimiz kerak bo’ladi. Keling, buni biroz kengaytirish mumkinligini ko’rib chiqaylik. Bunga konfiguratsiya obyekti orqali router’larni komponentlarga bog’lash yoki fayl tizimiga asoslangan router’lash kabi bir nechta usullarda erishishimiz mumkin. Ta’lim maqsadida (va, albatta, qiziqish uchun), fayl tizimiga asoslangan router’lashni o’rganamiz. Bu Next.js kabi freymvorklar tomonidan kiritilgan konvensiya va fikrlarning sababi va mexanizmini aniqroq tushunishga yordam beradi.
Agar biz barcha sahifalar ./pages katalogida joylashishi va ushbu katalogdagi barcha fayl nomlari router yo’llariga aylanishi kerak degan qoidani qo’llasak, serverimiz bu qoidaga asoslanib ishlaydi va kengaytirish imkoniyati oshadi.
Katalog strukturasini kengaytirish
Buni misol yordamida ko’rsatamiz. Avval katalog tuzilmasini yangilaymiz. Yangi katalog tuzilmasi quyidagicha ko’rinadi:
index.js
pages/
list.js
detail.js
dist/
clientBundle.js
Endi pages
katalogidagi har bir narsa route’ga aylanadi, va serverimizni ushbu tuzilmaga moslashtirishimiz mumkin.
import express from "express";
import { join } from "path";
import { renderToString } from "react-dom/server"; // 6-bobda yoritilgan
const app = express();
app.use(express.static("./dist")); // Statik fayllarni, masalan, klient JS’larini olish uchun
const createLayout = (children) => `<html lang="en">
<head>
<title>Mening sahifam</title>
</head>
<body>
${children}
<script src="/clientBundle.js"></script>
</body>
<html>`;
app.get("/:route", async (req, res) => {
// `pages` katalogidan route komponentini import qilamiz
const exportedStuff = await import(
join(process.cwd(), "pages", req.params.route)
);
// Nomlangan eksportlardan foydalanib bo'lmaydi, shuning uchun
// default eksportdan foydalanamiz.
// `.default` standartlashtirilgan, shuning uchun bunga suyansak bo'ladi
const Page = exportedStuff.default;
// Props’larni 'query string'dan olishimiz mumkin.
const props = req.query;
res.setHeader("Content-Type", "text/html");
res.end(createLayout(renderToString(<Page {...props} />)));
});
app.listen(3000, () => {
console.info("Ilova tinglanyapti!");
});
Kengaytirilgan strukturaning afzalliklari
Endi serverimiz ./pages katalogidagi konvensiya tufayli ancha kengaytirilgan! Bu yaxshi! Biroq, endi har bir sahifa komponentini default eksport qilishimiz kerak bo’ladi, chunki bizning yondashuvimiz umumiyroq bo’lib qoladi va import qilish uchun nomni oldindan bilishning imkoni bo’lmaydi. Bu freymvorklar bilan ishlashning ba’zi ijobiy va salbiy jihatlaridan biridir, ammo bu holatda, ushbu jihatlar foydali ko’rinmoqda.
Ma’lumotlarni olish (data fetching)
Ajoyib! Biz serverda render qilish va fayl tizimiga asoslangan route’lashni qo’shdik, lekin hali ham tarmoqda yuzaga kelayotgan “tarmoq sharsharalari”ga duch kelyapmiz. Keling, ma’lumot olish jarayonini yaxshilaylik. Avvalo, komponentlarimizni dastlabki ma’lumotlarni props orqali olishga moslashtiramiz. Soddalashtirish uchun faqat List
komponenti bilan shug’ullanamiz va Detail
komponentini sizga uyga vazifa sifatida qoldiramiz:
// Fayl tizimiga asoslangan route’lash uchun default eksportdan foydalanamiz.
export default function List({ initialThings } /* <- dastlabki prop qo'shilmoqda */) {
const [things, setThings] = useState(initialThings);
const [requestState, setRequestState] = useState("initial");
const [error, setError] = useState(null);
// Agar kerak bo'lsa, bu funksiya hali ham ma'lumotlarni olish uchun ishlashi mumkin.
useEffect(() => {
if (initialThings) return;
setRequestState("loading");
fetch("https://api.com/get-list")
.then((r) => r.json())
.then(setThings)
.then(() => {
setRequestState("success");
})
.catch((e) => {
setRequestState("error");
setError(e);
});
}, [initialThings]);
return (
<div>
<ul>
{things.map((thing) => (
<li key={thing.id}>{thing.label}</li>
))}
</ul>
</div>
);
}
Endi biz dastlabki prop qo’shganimiz uchun, bu sahifa uchun kerakli ma’lumotlarni serverda olish va renderlashdan oldin komponentga uzatish usulini izlashimiz kerak. Ideal holda, quyidagi tarzda harakat qilamiz:
import express from "express";
import { join } from "path";
import { renderToString } from "react-dom/server"; // 6-bobda yoritilgan
const app = express();
app.use(express.static("./dist")); // Statik fayllar, masalan, klient JS’larini olish uchun
const createLayout = (children) => `<html lang="en">
<head>
<title>Mening sahifam</title>
</head>
<body>
${children}
<script src="/clientBundle.js"></script>
</body>
<html>`;
app.get("/:route", async (req, res) => {
const exportedStuff = await import(
join(process.cwd(), "pages", req.params.route)
);
const Page = exportedStuff.default;
// Komponent ma'lumotlarini olish
const data = await exportedStuff.getData();
const props = req.query;
res.setHeader("Content-Type", "text/html");
// `props` va `data`ni uzatamiz
res.end(createLayout(renderToString(<Page {...props} {...data.props} />)));
});
app.listen(3000, () => {
console.info("Ilova tinglayapti!");
});
Bu shuni anglatadiki, biz ma’lumotlarga muhtoj bo’lgan har qanday sahifa komponentlaridan getData
deb nomlangan oluvchi funksiyani eksport qilishimiz kerak bo’ladi! Buning uchun ro’yxatni sozlaymiz:
// Biz buni serverda chaqiramiz va ushbu `props`larni komponentga uzatamiz
export const getData = async () => {
return {
props: {
initialThings: await fetch("https://api.com/get-list").then((r) => r.json()),
},
};
};
export default function List({ initialThings } /* <- dastlabki prop qo'shildi */) {
const [things, setThings] = useState(initialThings);
const [requestState, setRequestState] = useState("initial");
const [error, setError] = useState(null);
// Agar kerak bo'lsa, bu funksiya hali ham ma'lumotlarni olish uchun ishlashi mumkin.
useEffect(() => {
if (initialThings) return;
setRequestState("loading");
getData()
.then(setThings)
.then(() => {
setRequestState("success");
})
.catch((e) => {
setRequestState("error");
setError(e);
});
}, [initialThings]);
return (
<div>
<ul>
{things.map((thing) => (
<li key={thing.id}>{thing.label}</li>
))}
</ul>
</div>
);
}
Yakuniy Natija
Tayyor! Endi biz:
- Har bir route uchun serverda imkon qadar tezroq ma’lumotlarni olishni boshlaymiz
- Butun sahifani HTML satri ko’rinishida render qilamiz
- Ushbu HTML’ni klientga yuboramiz
Biz turli xil freymvorklardan uchta xususiyatni o’z ilovamizga muvaffaqiyatli qo’shdik va ularning asosiy versiyalarini implementatsiya qildik. Shu orqali freymvorklarning qanday ishlashini va ularning asosiy mexanizmlarini o’rgandik. Xususan, quyidagilarni bilib oldik:
- Freymvorklar bizga serverda render qilish imkonini beradi
- Fayl tizimiga asoslangan izomorf router’lashdan foydalanadi
- Ma’lumotlarni eksport qilingan funksiyalar orqali olish imkonini beradi
Agar siz ilgari Next.js ning 13-versiyasidan avvalgi versiyalaridan foydalangan bo’lsangiz, undagi quyidagi ko’rinishlarning sabablari yanada ravshan bo’ladi:
- ./pages katalogi
- Barcha sahifa eksportlari default eksportlar bo’lishi
getServerSideProps
vagetStaticProps
funksiyalari
Endi freymvorklarning kod darajasidagi mexanizmlarini va ba’zi konvensiyalarining sabablarini tushunib oldik. Keling, freymvorkdan foydalanishning umumiy afzalliklarini qisqacha ko’rib chiqamiz.