Ichki ishlash mexanizmi

Qisqa va ba’zan soddalashtirilgan javob: ha, turnServerComponentsIntoTreeOfElements bu React renderer’i hisoblanadi. U yuqori darajadan, masalan, <App /> dan boshlanib, rekursiv ravishda React daraxtiga kirib boradi va har bir komponentni chaqirib, uning qaytaradigan React elementi(oddiy JavaScript obyekti)ni oladi.

Keling, buni amalga oshirishning namunaviy kodini ko’rib chiqaylik va uning nima qilishi haqida muhokama qilaylik:

async function turnServerComponentsIntoTreeOfElements(jsx) {
  if (
    typeof jsx === "string" ||
    typeof jsx === "number" ||
    typeof jsx === "boolean" ||
    jsx == null
  ) {
    // Bu turdagi qiymatlar bilan hech narsa qilish shart emas.
    return jsx;
  }
 
  if (Array.isArray(jsx)) {
    // Massivdagi har bir elementni qayta ishlash.
    return await Promise.all(jsx.map(renderJSXToClientJSX(child)));
  }
 
  // Agar obyekt bilan ishlayotgan bo'lsak
  if (jsx != null && typeof jsx === "object") {
    // Agar bu React elementi bo'lsa,
    if (jsx.$$typeof === Symbol.for("react.element")) {
      // `{ type }` o'zgaruvchisi ichki o'rnatilgan komponentlar uchun string.
      if (typeof jsx.type === "string") {
        // Bu `<div />` kabi ichki o'rnatilgan komponent.
        // JSON formatiga o'tishi mumkin bo'lgan props’larni tekshiramiz.
        return {
          ...jsx,
          props: await renderJSXToClientJSX(jsx.props),
        };
      }
 
      if (typeof jsx.type === "function") {
        // Bu `<Footer />` kabi maxsus React komponenti.
        // Uning funksiyasini chaqirib, qaytarilgan JSX uchun jarayonni takrorlaymiz.
        const Component = jsx.type;
        const props = jsx.props;
        const returnedJsx = await Component(props);
        return await renderJSXToClientJSX(returnedJsx);
      }
      throw new Error("Not implemented.");
    } else {
      // Bu oddiy obyekt (props yoki ulardagi biror narsa).
      // Obyekt, lekin bu React elementi emas (yuqoridagi holatni hal qildik).
      // Har bir qiymatni ko'rib chiqamiz va ichidagi JSX’ni qayta ishlaymiz.
      return Object.fromEntries(
        await Promise.all(
          Object.entries(jsx).map(async ([propName, value]) => [
            propName,
            await renderJSXToClientJSX(value),
          ])
        )
      );
    }
  }
  throw new Error("Not implemented");
}

Ushbu kod parchasini ko’rganingizda, u biroz qo’rqinchli ko’rinishi mumkin bo’lsa-da, aniq aytadigan bo’lsak: bu faqatgina argumentlarga asoslangan katta if/else daraxtidir. Keling, har bir tarmoqni ko’rib chiqaylik va uning qanday ishlashini tushunamiz, avval input argumenti jsxdan boshlaymiz.

Birinchi tarmoq(branch)

Birinchi tarmoq uchun, agar biz React elementini quyidagicha hisoblasak:

<div>hi!</div>

Bu yerda "hi!" bolasi faqatgina string. Agar biz bu string’ni server komponenti renderer’iga topshirsak, uni shunday qoldirishni xohlaymiz. G’oya shundaki, server va klient tomonida React tushunishi mumkin bo’lgan turdagi narsalarni qaytarishdir. React, string, number va boolean’larni server va klient tomonida tushunishi va render qilishi mumkin, shuning uchun ularni shunday qoldiramiz.

Massivlarni qayta ishlash

Keyingi bosqichda, agar bizda massiv bo’lsa, uni .map metodi orqali yurib chiqamiz va har bir elementni rekursiv ravishda bizning funksiyamiz orqali qayta ishlaymiz. Massivlar bir qancha bolalarni o’z ichiga olishi mumkin, masalan:

[
  <div>hi</div>,
  <h1>hello</h1>,
  <span>love u</span>,
  (props) => <p id={props.id}>lorem ipsum</p>,
];

Masalan, Fragment’lar bolalarni massiv sifatida ifodalaydi. Shunday qilib, biz shunchaki ularni har bir bola uchun rekursiv chaqirib qayta ishlaymiz va davom etamiz.

Obyektlar bilan ishlash

Keyingi qismda juda qiziqarli bo’ladi: obyektlarni qayta ishlaymiz. Unutmangki, barcha React elementlari obyektlardir, lekin barcha obyektlar React elementlari emas. Qanday qilib biz obyektning React elementi ekanligini bilamiz? U $$typeof xususiyatiga ega bo’lib, bu symbol qiymatini oladi — aniqrog’i, Symbol.for('react.element'). Shuning uchun, biz obyekt bu kalit/qiymat juftligiga ega ekanligini tekshiramiz va agar mavjud bo’lsa, uni React elementi sifatida qayta ishlaymiz. Bu qismdagi kodda buni qilamiz:

if (jsx.$$typeof === Symbol.for("react.element")) {
  if (typeof jsx.type === "string") {
    // Bu <div /> kabi komponent.
    // JSON’ga aylantira olishimizni tekshirish uchun uning props’larini ko'rib chiqamiz.
    return {
      ...jsx,
      props: await renderJSXToClientJSX(jsx.props),
    };
  }
  if (typeof jsx.type === "function") {
    // Bu <Footer /> kabi maxsus React komponenti.
    // Uning funksiyasini chaqirib, qaytarilgan JSX uchun jarayonni takrorlaymiz.
    const Component = jsx.type;
    const props = jsx.props;
    const returnedJsx = await Component(props);
    return renderJSXToClientJSX(returnedJsx);
  }
  throw new Error("Not implemented.");
} else {
  // Bu oddiy obyekt (props yoki ulardagi biror narsa).
  // Har bir qiymatni ko'rib chiqamiz va ichidagi JSX’ni qayta ishlaymiz.
  return Object.fromEntries(
    await Promise.all(
      Object.entries(jsx).map(async ([propName, value]) => [
        propName,
        await renderJSXToClientJSX(value),
      ])
    )
  );
}

Yana bir tekshiruv

Agar shartli if bayonotining haqiqiy tarmog’ida bo’lsak, yana bir tekshiruv o’tkazamiz: jsx.type qiymati "string" yoki "function"mi? Buni biz React elementlari ikkisini ham tur sifatida olishi mumkinligi uchun qilamiz. String’lar oddiy DOM elementlari, masalan, "div", "span" kabi ishlatiladi. Funksiyalar esa maxsus komponentlar, masalan, <Footer /> uchun ishlatiladi. Agar bu string bo’lsa, u oddiy DOM elementi ekanligini bilamiz, shuning uchun uni shunday qoldiramiz, lekin uning props’larini rekursiv chaqiramiz — chunki uning props’lari bolalarga ega bo’lishi mumkin bo’lgan concurrent React komponentidir. Agar bu funksiya bo’lsa, u maxsus komponent ekanligini bilamiz, shuning uchun uni props’lari bilan chaqiramiz va rekursiv ravishda qaytarilgan JSX ustida funksiyamizni chaqiramiz. Bu jarayon oxir-oqibatda string, number, boolean, yoki ushbu turlardan iborat array yoki string turidagi React elementi qaytarguncha davom etadi. Bu boshqa tarmoqqa tushishi mumkin.

await va server komponentlari

Biz funksional komponentni chaqirishimizdan oldin awaitni e’tiborga olishimiz kerak. Bu server tomonida bajarilayotgani sababli, agar bu server komponenti bo’lsa, funksional komponentni kutishimiz mumkin! Server komponentlarining sirli tomoni shundaki, biz ularni serverda await orqali kutishimiz mumkin va ular bizga React elementini qaytaradi. Keyinchalik, biz uni renderToString yoki renderToPipeableStreamga uzatib, klientga yuborilishi mumkin bo’lgan string yoki string’lar oqimiga render qilishimiz mumkin. Haqiqatan ham, bizning funksiyamiz shuni amalga oshiradi: u rekursiv ravishda barcha asinxron narsalarni kutmoqda, natijada elementlar daraxtini (JavaScript obyekti) ishlab chiqaradi va uning barcha ma’lumotlar qaramliklari hal qilinadi.

Oddiy obyektlar bilan ishlash

Nihoyat, agar obyekt React elementi bo’lmasa, demak, u oddiy obyekt ekanligini bilamiz, shuning uchun biz rekursiv ravishda funksiyamizni obyekt ichidagi har bir qiymatga chaqiramiz va natijani qaytaramiz. Odatda, obyekt faqat props’larni o’z ichiga oladi, shuning uchun else tarmog’ida, biz shunchaki har bir prop qiymatiga rekursiv ravishda funksiyamizni chaqiramiz va natijani qaytaramiz, bu esa render props kabi pattern’lar orqali props sifatida berilishi mumkin bo’lgan har qanday komponentlarni samarali tarzda ochadi, bu haqida 5-bobda muhokama qilingan.

Minimal RSCs Renderer

Shu bilan, bizning minimal RSCs renderer’imiz tugadi. Bu mukammal emas, lekin yaxshi boshlanishdir. Biz uni server komponentlarimizni React elementlariga render qilish uchun ishlatishimiz mumkin, keyin esa ularni klientlarga yuboramiz.

Bu jarayonni yakunlagach, biz uni renderToString yoki renderToPipeableStreamga uzatyapmiz, yoki hatto uni seriyalashtirib, to’g’ridan-to’g’ri brauzerga yuborishimiz mumkin. Klient tomonida React uni render qilishi mumkin, chunki bu, asosan, React tushunishi mumkin bo’lgan React elementlaridan iborat daraxtdir. Biroq, bu jarayonda hal qilishimiz kerak bo’lgan yana bir muammo bor: seriyalashtirish.