DokumentatsiyaReactFreymvorklarNega bizga freymvork kerak

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:

index.js
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} />
);
List.js
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>
  );
};
Detail.js
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:

server.js
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.

server.js
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:

./pages/list.jsx
// 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:

./server.js
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:

./pages/list.jsx
// 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 va getStaticProps funksiyalari

Endi freymvorklarning kod darajasidagi mexanizmlarini va ba’zi konvensiyalarining sabablarini tushunib oldik. Keling, freymvorkdan foydalanishning umumiy afzalliklarini qisqacha ko’rib chiqamiz.