משחקים עם React Server Components
פיצ'ר 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
לשאלות שלו אני עונה:
- שימוש ב TypeScript - כן
- שימוש ב ESLint - כן
- שימוש בתיקיית src - כן
- שימוש בתיקיית app ניסיונית - כן
- שימוש ב 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 פותר שתי בעיות מרכזיות בפיתוח עם ריאקט:
הוא מסדר את כל שליפות המידע הראשוניות שאנחנו צריכים כדי להציג את העמוד, וחוסך למשוך מידע מצד הלקוח אחרי שהעמוד עולה.
הוא מאפשר חיבור קל יותר בין קומפוננטות ריאקט למידע שמגיע מבסיס נתונים כשהמידע הזה דרוש רק לטעינה ראשונית של העמוד.
הרבה אתרים סטטיים או סטטיים ברובם יוכלו להרוויח מהשינוי החדש, ובמיוחד מאחר וב next.js קומפוננטות צד שרת הן ברירת המחדל.