• בלוג
  • משחקים עם React Server Components

משחקים עם React Server Components

05/04/2023

פיצ'ר Server Components של ריאקט הוא חלק מריאקט 18 ומאפשר סוג של Server Side Rendering לקומפוננטות ספציפיות. בגירסה 13 של Next.JS התווספה תמיכה ב Server Components מה שהפך את העבודה איתם להרבה יותר פשוטה. בואו נראה איך זה עובד ומה היתרונות שלהם על פני פיתוח ריאקט רגיל או על פני Server Side Rendering.

1. מהם React Server Components

קומפוננטות צד שרת הן פשוט קומפוננטות ריאקט שמרונדרות לגמרי בצד שרת. המפתח כאן הוא השורה "קומפוננטות צד שרת לא משפיעות כלל על גודל הבאנדל שנשלח ללקוח" איתה נפתח ה RFC. המטרה של קומפוננטות צד שרת היא לאפשר לכתוב קוד צד שרת בסגנון קומפוננטות ריאקט, וכך לספק חווית פיתוח אוניברסלית טובה יותר.

2. ה Server Component הראשון שלי

בשביל להבין איך זה עובד נלך לבנות מערכת עם Server Components ב Next.JS. אני מתחיל פרויקט next חדש:

$ npx create-next-app@latest server-components-demo

לשאלות שלו אני עונה:

  1. שימוש ב TypeScript - כן
  2. שימוש ב ESLint - כן
  3. שימוש בתיקיית src - כן
  4. שימוש בתיקיית app ניסיונית - כן
  5. שימוש ב import alias ברירת המחדל

קיבלתי עץ תיקיות שנראה כך:

.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
│   ├── next.svg
│   ├── thirteen.svg
│   └── vercel.svg
├── src
│   └── app
│       ├── api
│       │   └── hello
│       │       └── route.ts
│       ├── favicon.ico
│       ├── globals.css
│       ├── layout.tsx
│       ├── page.module.css
│       └── page.tsx
└── tsconfig.json

6 directories, 15 files

בשלב ראשון אני מוסיף תיקיה בשם components ובתוכה יוצר קומפוננטה בשם HelloWorld עם הקוד הבא:

// file: src/app/components/hello_world.tsx

export default function HelloWorld({name="Guest"}) {
  return <h1>Hello {name}</h1>
}

לאחר מכן אני מעדכן את הקובץ src/page.tsx לקוד הבא:

import HelloWorld from './components/hello_world'

export default function Home() {
  return (
    <main>
      <HelloWorld name="Dave" />
      <HelloWorld name="Dana" />
      <HelloWorld />
    </main>
  )
}

ואנחנו מוכנים לצאת לדרך. אני מפעיל את הקוד עם:

$ npm run dev

ויכול לגלוש ל loalhost:3000 כדי לראות את הודעות הברכה לשלושת המשתמשים.

בשביל לראות את הקוד בפרודקשן אני מעלה אותו למאגר גיט בכתובת: https://github.com/ynonp/server-components-demo

מחבר אותו ל vercel ויש לנו את המערכת באוויר בכתובת: https://server-components-demo-aabippwya-ynonp.vercel.app

הפתעה ראשונה שתקבל את פנינו בכניסה לעמוד היא שאין צורך בהרצת JavaScript כדי לראות את הדף. גם אם אתם נכנסים עם תוסף NoScript או משהו דומה אתם עדיין תראו את אותן שלוש שורות של Hello, כי הרינדור של הקומפוננטות נעשה בצד שרת. נכון שספריית ריאקט עצמה עדיין נשלחת ללקוח, אבל קוד הקומפוננטות כבר לא.

אפשר לראות ההבדל אם נעדכן את הקוד של Hello World ונהפוך אותו לקומפוננטת צד לקוח. בשביל זה יש להוסיף את הטקסט "use client" בראש הקובץ בצורה כזאת:

"use client";

export default function HelloWorld({name="Guest"}) {
  return <h1>Hello {name}</h1>
}

אחרי קומיט והעלאת הגירסה אני יכול לזהות בקובץ page.js בשרת את הקוד הבא:

    7126: function(e, r, t) {
        "use strict";
        t.r(r),
        t.d(r, {
            default: function() {
                return o
            }
        });
        var n = t(9268);
        function o(e) {
            let {name: r="Guest"} = e;
            return (0,
            n.jsxs)("h1", {
                children: ["Hello ", r]
            })
        }
    },

שמראה לי שהקוד של הקומפוננטה נשלח ללקוח.

כן צריך להגיד שגם בגירסה הזו של ה Client Component עדיין אפשר לגשת לעמוד ולראות את כל התוכן גם בלי JavaScript מופעל, בזכות ה Server Side Rendering שאני מקבל מ next.js. במקרה הזה ההבדל המרכזי בין שתי הגישות היה גודל הבאנדל והתוכן שלו.

אפשר לראות את הגירסה עם Client Component בקישור הזה: https://server-components-demo-2cr9zxka4-ynonp.vercel.app/

3. שליפת מידע מ API ב Server Component

אחד היתרונות המעניינים של הרצת קוד צד-שרת ושליחת התוצאה בלבד לקלאיינט הוא היכולת לגשת ל APIs או לבסיס נתונים מתוך קומפוננטת ריאקט, גישה שתתבצע רק בצד השרת. ננסה את זה עם API. ניצור קומפוננטה להצגת מידע מ swapi.dev שתציג מידע על כוכבים ממלחמת הכוכבים:

export default async function PlanetInfo({id=3}) {
  const res = await fetch(`https://swapi.dev/api/planets/${id}/`);
  const data = await res.json();

  return (<div>
    <p>Welcome to the planet {data.name}. Its diameter is {data.diameter} and its population is {data.population} inhabitants</p>
  </div>)
}

אני מעדכן את קוד העמוד שיציג לי מידע על כמה כוכבים בקובץ page.tsx:

import PlanetInfo from './components/planet_info'

export default function Home() {
  return (
    <main>
      <PlanetInfo id={3} />
      <PlanetInfo id={4} />
    </main>
  )
}

וכבר במצב פיתוח אפשר לראות שקרה פה משהו מעניין - בטאב network אני לא רואה בכלל גישות ל swapi. המידע כולו הגיע מקומפוננטת צד השרת בטעינת העמוד, למרות שהקומפוננטה היא בכלל Promise ולא מתנהגת לפי מנגנון מחזור החיים הרגיל של קומפוננטת ריאקט.

למעשה אנחנו רואים כאן את היכולת המלהיבה השניה של קומפוננטות צד שרת, והיא האפשרות "לברוח" מדרך החשיבה הריאקטית ולהשתמש בקוד async/await רגיל כדי למשוך מידע ממקורות חיצוניים ולהכין את הקומפוננטה.

4. מגבלות - העברת מידע מ Client Component ל Server Component

בשיחה על Server Components שווה לשים לב גם למגבלות שלהם, ובפרט למגבלה (היחסית צפויה) ש Server Components לא כוללים סטייט. בדוגמה של כוכבי הלכת ממלחמת הכוכבים, ייתכן ונרצה לאפשר למשתמש לבחור את מזהה הכוכב ואז להציג את המידע על אותו כוכב.

זה הקוד שהייתי רוצה לכתוב בקובץ planet_picker.tsx:

"use client";
import { useState } from "react";
import PlanetInfo from "./planet_info";

export default function PlanetPicker() {
  const [id, setId] = useState(3);
  return (
    <div>
      <input type="text" value={id} onChange={(e) => setId(Number(e.target.value))} />
      <PlanetInfo id={id} />
    </div>
  )
}

אבל זה לא עובד - ה Server Component לא יודע עדיין מה המשתמש בחר בזמן יצירת העמוד (כי משתמש עדיין לא בחר כוכב) ולכן זה לא עובד. המנגנון הכי קרוב שיש ל next להציע הוא להטמיע את הסטייט, במקרה שלנו ה id של הכוכב, ב url של העמוד ואז כשמשתמש משנה את הערך להשתמש ב router.push כדי לעבור לעמוד חדש, שם הקומפוננטה תרונדר בצד השרת עם הערך העדכני שיילקח מה Route Param.

5. סיכום

סך הכל פיצ'ר Server Components פותר שתי בעיות מרכזיות בפיתוח עם ריאקט:

  1. הוא מסדר את כל שליפות המידע הראשוניות שאנחנו צריכים כדי להציג את העמוד, וחוסך למשוך מידע מצד הלקוח אחרי שהעמוד עולה.

  2. הוא מאפשר חיבור קל יותר בין קומפוננטות ריאקט למידע שמגיע מבסיס נתונים כשהמידע הזה דרוש רק לטעינה ראשונית של העמוד.

הרבה אתרים סטטיים או סטטיים ברובם יוכלו להרוויח מהשינוי החדש, ובמיוחד מאחר וב next.js קומפוננטות צד שרת הן ברירת המחדל.