Remix freymvorki

Remix — bu zamonaviy va kuchli bo’lgan veb freymvorki bo’lib, React va veb platformasining imkoniyatlaridan foydalanadi. Keling, qanday ishlashini tushunish uchun amaliy misollar bilan tanishamiz.

Asosiy Remix ilovasi

Avval biz asosiy Remix ilovasini o’rnatamiz. Remix’ni npm yordamida o’rnatishingiz mumkin:

npm create remix@2.2.0

Bu sizning joriy katalogingizda yangi Remix loyihasini yaratadi. Keling, ichiga nazar solamiz. Avvalambor, bizda app katalogi mavjud bo’lib, unda entry.client.tsx va entry.server.tsx fayllari bor. Shuningdek, ushbu katalogda root.tsx ham mavjud.

Darhol ko’rinib turibdiki, Remix dastlabki holatda klient va server kirish nuqtalarini qo’llab-quvvatlaydi. Bundan tashqari, root.tsx fayli har bir sahifada render qilinadigan umumiy layout komponentini o’z ichiga oladi. Bu, Remix’ning sizga tezda boshlashishingizga yordam beradigan oldindan belgilangan strukturani taqdim etishining ajoyib namunasidir.

Server rendering

Remix entry.server.tsx orqali serverda render qilish texnikasini standart holatda taqdim etadi. Fayl biz uchun avtomatik ravishda yaratilgan, lekin uning qanday ishlashini bir oz tushunishga harakat qilamiz. Mana bu yerda, uning ko’rinishi keltirilgan:

import { PassThrough } from "node:stream";
 
import type { AppLoadContext, EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import isbot from "isbot";
import { renderToPipeableStream } from "react-dom/server";
 
const ABORT_DELAY = 5_000;
 
export default function handleRequest(
    request: Request,
    responseStatusCode: number,
    responseHeaders: Headers,
    remixContext: EntryContext,
    loadContext: AppLoadContext
) {
    return isbot(request.headers.get("user-agent"))
        ? handleBotRequest(
            request,
            responseStatusCode,
            responseHeaders,
            remixContext
        )
        : handleBrowserRequest(
            request,
            responseStatusCode,
            responseHeaders,
            remixContext
        );
}
 
function handleBotRequest(
    request: Request,
    responseStatusCode: number,
    responseHeaders: Headers,
    remixContext: EntryContext
) {
    return new Promise((resolve, reject) => {
        let shellRendered = false;
        const { pipe, abort } = renderToPipeableStream(
            <RemixServer
                context={remixContext}
                url={request.url}
                abortDelay={ABORT_DELAY}
            />,
            {
                onAllReady() {
                    shellRendered = true;
                    const body = new PassThrough();
                    const stream = createReadableStreamFromReadable(body);
 
                    responseHeaders.set("Content-Type", "text/html");
 
                    resolve(
                        new Response(stream, {
                            headers: responseHeaders,
                            status: responseStatusCode,
                        })
                    );
 
                    pipe(body);
                },
                onShellError(error: unknown) {
                    reject(error);
                },
                onError(error: unknown) {
                    responseStatusCode = 500;
                    // Shell ichidan streaming render xatolarini yozib olish. 
                    // Dastlabki shell(qobiq)ni render qilishda duch kelgan xatolarni yozmang
                    // chunki ular rad etadi va handleDocumentRequest tizimiga kiradi.
                    if (shellRendered) {
                        console.error(error);
                    }
                },
            }
        );
    });
 
    setTimeout(abort, ABORT_DELAY);
}
 
function handleBrowserRequest(
    request: Request,
    responseStatusCode: number,
    responseHeaders: Headers,
    remixContext: EntryContext
) {
    return new Promise((resolve, reject) => {
        let shellRendered = false;
        const { pipe, abort } = renderToPipeableStream(
            <RemixServer
                context={remixContext}
                url={request.url}
                abortDelay={ABORT_DELAY}
            />,
            {
                onShellReady() {
                    shellRendered = true;
                    const body = new PassThrough();
                    const stream = createReadableStreamFromReadable(body);
 
                    responseHeaders.set("Content-Type", "text/html");
 
                    resolve(
                        new Response(stream, {
                            headers: responseHeaders,
                            status: responseStatusCode,
                        })
                    );
 
                    pipe(body);
                },
                onShellError(error: unknown) {
                    reject(error);
                },
                onError(error: unknown) {
                    responseStatusCode = 500;
                    // Shell ichidan streaming render xatolarini yozib olish. 
                    // Dastlabki shell(qobiq)ni render qilishda duch kelgan xatolarni yozmang
                    // chunki ular rad etadi va handleDocumentRequest tizimiga kiradi.
                    if (shellRendered) {
                        console.error(error);
                    }
                },
            }
        );
    });
 
    setTimeout(abort, ABORT_DELAY);
}

Kodni tushunib chiqish

Remix haqida juda yaxshi narsa shundaki, ushbu fayl ichki foydalanish uchun mo’ljallangan, lekin biz uni sozlash uchun bu yerda ochiq holda taqdim etilgan. Agar biz ushbu faylni o’chirsak, Remix ushbu faylning ichki standart implementatsiyasiga murojaat qiladi. Bu, agar kerak bo’lsa, server rendering xatti-harakatini sozlashimizga imkon beruvchi yaxshi chiqish yo’li. Shunday qilib, bizni freymvorkning “sehri”ga bog’lab qo’ymaydi.

Ushbu fayl HTTP javoblari qanday yaratilishi va boshqarilishi kerakligini belgilaydi, xususan, botlar va oddiy brauzerlar uchun so’rovlar qanday boshqarilishini ta’riflaydi. Remix — bu zamonaviy React ilovalarini qurish uchun mo’ljallangan freymvork va ushbu fayl Remix ilovasining server tomoni logikasining bir qismidir.

Dastlab, fayl kerakli modullar va tiplarni turli kutubxonalar, masalan, node:stream, @remix-run/node, @remix-run/react, isbot va react-dom/serverdan import qiladi. U 5,000 millisekund qiymatiga ega ABORT_DELAY o’zgaruvchisini belgilaydi, bu esa renderlash operatsiyalari uchun timeout muddati sifatida ishlatiladi.

Fayl bir nechta argumentlarni qabul qiluvchi handleRequest funksiyasini eksport qiladi, ularga HTTP so’rovi, javob status kodi, javob sarlavha(header)lari va Remix va ilovaning yuklash jarayonlari uchun kontekstlar kiradi. handleRequest ichida, kelayotgan so’rovning user-agent’ini tekshirib, isbot kutubxonasidan foydalanib, bu botdan kelayotganini yoki yo’qligini aniqlaydi. So’rov botdan yoki brauzerdan kelishiga qarab, uni mos ravishda handleBotRequest yoki handleBrowserRequest funksiyalariga yo’naltiradi.

SEO va ishlash samaradorligiga yordam

Bu SEO va ishlash samaradorligiga yordam beradi. Masalan, agar so’rov botdan kelsa, javobda sahifaning render qilingan HTML kontenti bo’lishi muhim, bu esa handleBotRequest funksiyasining vazifasidir. Aksincha, agar so’rov oddiy brauzerdan kelsa, sahifaning render qilingan HTML kontenti va sahifani hidratsiya qilish uchun zarur JavaScript kodini o’z ichiga olishi muhim, bu esa handleBrowserRequest funksiyasining vazifasidir. Remix biz uchun buni avtomatik ravishda bajarishi juda ajoyib.

handleBotRequest va handleBrowserRequest funksiyalari tuzilish jihatidan deyarli o’xshash, ammo render qobiq tayyor bo’lganida yoki xatoga duch kelganda har xil ishlov beruvchi, ya’ni handler’larga, ega. Ular HTTP javobiga ega bo’lgan promise qaytaradi. Ular renderToPipeableStream yordamida o’tkaziluvchan oqim(pipeable stream)ga render operatsiyasini boshlaydi, so’rovdan olingan zarur kontekst va URL bilan birga RemixServer komponentini uzatadi. Ular ABORT_DELAY dan ko’proq vaqt oladigan renderlash operatsiyasini bekor qilish uchun timeout belgilaydi.

Renderlash operatsiyasi uchun event handler’larida PassThrough stream’ini va undan olingan o’qiladigan stream’ni yaratadi. Ular javob uchun Content-Type sarlavhasini text/html sifatida o’rnatadi. Ular promise’ni yangi Response obyekti bilan hal qiladi, bu esa stream, javob sarlavhalari va status kodini o’z ichiga oladi. Renderlash davomida xatolar yuzaga kelsa, ular promise’ni rad qiladi yoki xatoni konsolga yozadi, xato qaysi renderlash bosqichida yuz berganiga qarab.

Ushbu fayl asosan HTTP javoblarining to’g’ri yaratilishini va qaytarilishini ta’minlaydi, botdan yoki oddiy brauzerdan kelayotgan so’rovga qarab turli renderlash logikasini qo’llaydi, bu esa zamonaviy veb-ilovalarda SEO va ishlash samaradorligiga oid masalalar uchun juda muhimdir.

Agar bizda hech qanday sozlashlar bo’lmasa, biz ushbu faylni o’chirib tashlaymiz, va Remix biz uchun server tomonda renderlashni hal qiladi. Hozircha uni saqlaymiz va Remix qanday qilib router’larni boshqarishini ko’rib chiqamiz.

Routing

Remix’da har bir routing routes katalogida joylashgan fayl bilan ifodalanadi. Agar biz ./routes/cheese.tsx faylini yaratib, uning default eksportini quyidagicha qilsak:

export default function CheesePage() {
  return <h1>This might sound cheesy, but I think you're really grate!</h1>;
}

va keyin lokal ishlab chiqish serverini npm run dev buyrug’i bilan ishga tushirsak, biz qiziqarli sarlavhaga ega sahifani ko’ramiz. Yana bir bor, Remix bizga tezda boshlashga yordam beradigan oldindan belgilangan strukturani taqdim etishini ko’ramiz va bu konvensiyada default eksportlarning qiymati bizning avvalgi fayl tizimi asosidagi routing qilishimizga o’xshashdir.

Bu ./app/root.tsx ichidagi umumiy layout komponenti va server va klient kirish nuqtalari bilan birlashganda, bu ko’pchilik veb-saytlarining asosini tashkil etadi. Biroq, biz zamonaviy veb-ilovalar uchun bir muhim komponentdan mahrummiz: ma’lumotlarni olish. Keling, Remix buni qanday hal qilishini ko’raylik.

Ma’lumotlarni olish (data fetching)

Remix’da ma’lumotlarni olish jarayoni yozilish vaqtida loader’lar, ya’ni yuklovchilar, deb nomlangan funksiyalarni ishlatishni o’z ichiga oladi. Agar siz loader deb nomlangan va ma’lum bir qiymat qaytaruvchi asinxron funksiyani eksport qilsangiz, bu qiymat sizning sahifa komponentingizda useLoaderData hook’i orqali mavjud bo’ladi. Keling, bu qanday ishlashini misol bilan ko’raylik.

Oldingi pishloq sahifasiga misolimizga qaytamiz, aytaylik, biz API’dan pishloqlar ro’yxatini olishni va ularni sahifada ko’rsatishni xohlaymiz. Buni ./routes/cheese.tsx faylidan loader funksiyasini eksport qilish orqali amalga oshirishimiz mumkin:

// Yordamchi funksiyalarni olish
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
 
// Loader
export async function loader() {
  const data = await fetch("https://api.com/get-cheeses");
  return json(await data.json());
}
 
export default function CheesePage() {
  const cheeses = useLoaderData();
  return (
    <div>
      <ul>
        <h1>This might sound cheesy, but I think you're really grate!</h1>
        {cheeses.map((cheese) => (
          <li key={cheese.id}>{cheese.name}</li>
        ))}
      </ul>
    </div>
  );
}

Ma’lumotlarni olish jarayonidagi takrorlanish

Bu bilan biz o’zimizning avvalgi ma’lumotlarni olish jarayonimizda takrorlanishni ko’ramiz. Remix loader funksiyasi bizning getData funksiyamizga o’xshashligini ko’rishimiz mumkin. Shuningdek, useLoaderData hook’i bizning initialThings prop’imizga o’xshashligini ham ko’ramiz.

Ideal holatda, hozirda biz freymvorklar ushbu xususiyatlarni qanday amalga oshirishini aks ettiruvchi umumiy pattern’lar va asosiy mexanizmlarni o’rganish imkoniyatiga egamiz.

Hozirga qadar biz bularni ko’rib chiqdik:

  • Server rendering
  • Routing
  • Data fetching

Biroq, biz hali to’xtab o’tmagan yana bir Remix xususiyati bor: formalar va server action’lar yoki mutatsiyalar — ya’ni, serverda ma’lumotlarni mutatsiya qilish, masalan, yaratish, yangilash yoki o’chirish. Keling, buni ko’rib chiqamiz.

Ma’lumotlarni mutatsiya qilish (mutating data)

Remix vebni uning asoslariga qaytarishga mas’ul bo’lib, native veb platformasi konvensiyalari va xatti-harakatlariga kuchli tayanadi. Buning eng yaxshi tomoni ma’lumotlarni mutatsiya qilishda va Remix’ning formalarni ishlatishida ko’rinadi. Keling, avvalgi pishloq misolimizni kengaytirib, pishloq ro’yxatini o’zgartiriladigan qilib qo’yamiz. Buni amalga oshirish uchun, ./routes/cheese.tsx faylini yangilaymiz:

// Yordamchi funksiyalarni olish
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
 
// Loader
export async function loader() {
  const data = await fetch("https://api.com/get-cheeses");
  return json(await data.json());
}
 
export default function CheesePage() {
  const cheeses = useLoaderData();
  return (
    <div>
      <ul>
        <h1>This might sound cheesy, but I think you're really grate!</h1>
        {cheeses.map((cheese) => (
          <li key={cheese.id}>{cheese.name}</li>
        ))}
      </ul>
      <form action="/cheese" method="post">
        <input type="text" name="cheese" />
        <button type="submit">Add Cheese</button>
      </form>
    </div>
  );
}

E’tiboringizni jalb qiladigan jihat shundaki, biz sahifaga yangi form elementi qo’shdik. Ushbu forma /cheese harakati va POST metodiga ega. Bu standart HTML formasi bo’lib, /cheese route’ga POST so’rovini yuboradi. Bundan tashqari, input elementi name atributiga ega va useState yoki onChange ishlov beruvchisiga ega emas: Remix brauzerga forma holatini va xatti-harakatlarini boshqarishiga imkon beradi. Bu Remix qanday qilib veb platformasidan foydalanib, ajoyib bo’lgan dasturchi bilan ishlash qulayligini taqdim etishi va g’ildirakni qaytadan kashf etishni o’rniga va React’ni har narsani boshqarishga majburlamasligi uchun ajoyib bir misoldir.

Forma orqali ma’lumotlarni yuborish

Forma uchun harakat(action) qiymati /cheesega teng va biz allaqachon ./routes/cheese.tsx faylida joylashganmiz, shuning uchun forma xuddi shu route’ga yuborilishini taxmin qilishimiz mumkin. Ushbu route’ga POST metodi bilan kirilganida, forma yuborilganini bilamiz. Standart bo’yicha GET metodi bilan kirilganda esa, formani yuborish hali amalga oshirilmaganligini bilamiz va dastlabki foydalanuvchi interfeysini ko’rsatamiz.

Keling, ./routes/cheese.tsx faylini yangilab, bu jarayonni boshqarishni qo’shamiz:

// Yordamchi funksiyalarni olish
import { json, ActionFunctionArgs, redirect } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
 
// Loader
export async function loader() {
  const data = await fetch("https://api.com/get-cheeses");
  return json(await data.json());
}
 
// Forma action funksiyasi
export async function action({ params, request }: ActionFunctionArgs) {
  const formData = await request.formData();
 
  await fetch("https://api.com/add-cheese", {
    method: "POST",
    body: JSON.stringify({
      name: formData.get("cheese"),
    }),
  });
 
  return redirect("/cheese"); // Bu sahifaga qaytadi, lekin bu safar GET bilan.
}
 
export default function CheesePage() {
  const cheeses = useLoaderData();
  return (
    <div>
      <h1>This might sound cheesy, but I think you're really grate!</h1>
      <ul>
        {cheeses.map((cheese) => (
          <li key={cheese.id}>{cheese.name}</li>
        ))}
      </ul>
      <form action="/cheese" method="post">
        <input type="text" name="cheese" />
        <button type="submit">Add Cheese</button>
      </form>
    </div>
  );
}

Diqqat qiling, biz params va request argumentlarini qabul qiladigan yangi action funksiyasini qo’shdik. params argumenti route’ning parametrlarini o’z ichiga olgan obyekt, request argumenti esa so’rov obyektini o’z ichiga oladi. Biz bu obyekt yordamida formadagi ma’lumotlarni olishimiz va yangi pishloq qo’shish uchun o’z API’mizga so’rov yuborishimiz mumkin.

Yangi ma’lumotlar yuklanishi

Keyin biz xuddi shu route’ga, ammo bu safar GET metodi bilan qayta yo’naltirishni amalga oshiramiz. Bu esa sahifaning qayta yuklanishiga sabab bo’ladi va loader funksiyasi qayta chaqiriladi, natijada yangilangan pishloqlar ro’yxati olinadi.

Remix JavaScript faqat kerak bo’lgan joyda ishlatilishini ta’minlash va qolgan qismini brauzer boshqarishiga ruxsat berish orqali to’liq veb platformasiga tayanadi. Agar ushbu sahifa JavaScript’siz ochilgan bo’lsa, baribir ishlaydi, chunki bu veb platformasiga tayanadi. Agar sahifa JavaScript’dan foydalansa, Remix interaktivlikni va yaxshiroq foydalanuvchi bilan ishlash qulayligini qo’shib, qulaylikni progressiv ravishda yaxshilaydi.

Remix umumiy ishlash prinsipi

  • Server tomonidan render qilishni ta’minlaydi
  • Route’larni boshqaradi
  • Ma’lumotlarni olishni (fetch) boshqaradi
  • Ma’lumotlarni o’zgartirishni (ya’ni mutatsiya) boshqaradi

Hozirda biz o’zimiz amalga oshirgan ushbu funksiyalar va Remix’ning ularni amalga oshirish usullari o’rtasidagi kuchli o’xshashliklarni ko’rgan bo’lishimiz kerak. Bu bizning freymvorklarda ushbu funksiyalarning qanday amalga oshirilishi mexanizmlarini tushunayotganimizni ko’rsatuvchi yaxshi belgi hisoblanadi.

Endi keling Next.js’ni ko’rib chiqaylik va u ushbu xususiyatlarni qanday qilib izolyatsiya qilish orqali amalga oshirayotganini ko’rib chiqamiz.