קומפוננטות צד-שרת, קומפוננטות צד-לקוח ו State
זוכרים את useState שלא עבד לנו בפרק הקודם? בפרק זה נבין למה ונלמד עוד טיפ על הארכיטקטורה של next.js.
1. קומפוננטת צד-שרת
בשביל לשפר ביצועים הארכיטקטורה הבסיסית של next.js מחלקת את קומפוננטות ריאקט שאנחנו כותבים לקומפוננטות צד-שרת וקומפוננטות צד-לקוח. קומפוננטות צד-לקוח זה מה שאנחנו מכירים מריאקט "הרגיל" עם תוספת של SSR, כלומר השרת לוקח את הקומפוננטה, הופך אותה ל HTML, שולח את זה לדפדפן יחד עם ה JavaScript של הקומפוננטה שם היא עוברת רינדור נוסף וקוד הטיפול באירועים "מתלבש" על ה HTML שנשלח מהשרת.
קומפוננטת צד שרת היא בסך הכל רעיון דומה חוץ מזה שמוותרים על החלק של לשלוח את ה JavaScript לדפדפן ולרנדר את הקומפוננטה שוב בצד הלקוח, כלומר נשארים רק עם HTML שנשלח מהשרת ללקוח.
מתוך הארכיטקטורה השונה נגזרות גם היכולות השונות:
קומפוננטות צד-לקוח יכולות להשתמש ב State, כיוון שדברים יכולים לקרות ולשנות את מה שמוצג למשתמש.
קומפוננטות צד-שרת לא יכולות להשתמש ב State, כי אין להן דרך להוסיף קוד טיפול באירועים. מצד שני קומפוננטות צד-שרת יכולות לגשת בצורה ישירה לבסיס הנתונים או למערכת ההפעלה של השרת, כי הקוד רץ על השרת בלבד.
הקומפוננטה page.tsx שכתבנו בשיעור הפתיחה היתה קומפוננטת צד שרת, כי זו ברירת המחדל של קומפוננטות ב next.js. בשביל להוסיף לה State אני צריך להפוך אותה לקומפוננטה צד-לקוח בעזרת הוספת השורה:
"use client";
בראש הקובץ.
לדוגמה אכתוב את התוכן הבא בקובץ page.tsx כדי להציג קומפוננטה של מונה לחיצות:
"use client";
import Image from 'next/image'
import { useState } from 'react';
export default function Home() {
const [count, setCount] = useState(0);
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<button onClick={() => setCount(c => c + 1)}>{count}</button>
</main>
)
}
אפשר גם לפצל את הקומפוננטה לכמה קומפוננטות כדי לשים מספר מונים על המסך:
"use client";
import { useState } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Counter />
<Counter />
<Counter />
</main>
)
}
2. שילוב קומפוננטות צד-שרת עם קומפוננטות צד-לקוח
בואו נעשה עוד ניסוי, נוציא את Counter לקובץ אחר בשם counter.tsx ואז בקובץ page.tsx נשאיר רק את הקוד הבא:
import Counter from './counter'
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<Counter />
<Counter />
<Counter />
</main>
)
}
זה אגב יהיה הקוד ב counter.tsx:
"use client";
import { useState } from 'react';
export default () => {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
כיף לשים לב שאין ל next שום בעיה כשאני משלב קומפוננטות צד-לקוח בתוך קומפוננטות צד-שרת. קומפוננטות צד הלקוח יישלחו לדפדפן וירונדרו גם שם, בעוד שהחלקים שהם קומפוננטות צד שרת ירונדרו רק בצד השרת.
מצב הפוך הוא קצת יותר מסובך. ניצור קובץ בשם header.tsx עם התוכן הבא:
export default () => (<h1>Counter Header</h1>);
ונעדכן את counter.tsx כך שיכיל את הקוד:
"use client";
import { useState } from 'react';
import Header from './header';
export default () => {
const [count, setCount] = useState(0);
return (
<div>
<Header />
<button onClick={() => setCount(c => c + 1)}>{count}</button>
</div>
)
}
למרות ש header.tsx לא כולל את הכותרת use client הוא עדיין קומפוננטת צד-לקוח, בגלל שעשיתי לו import מתוך קומפוננטת צד-לקוח. דרך קלה להיווכח בזה היא להוסיף console.log בתוך הקומפוננטה ולראות אם ההדפסה מופיעה גם בקונסול של הדפדפן וגם בחלון שורת הפקודה, או רק בחלון שורת הפקודה.
3. עכשיו אתם
הנה עוד כמה משחקים שכדאי לנסות עם קומפוננטות צד-שרת וקומפוננטות צד-לקוח כדי להבין טוב יותר את ההבדלים ביניהן:
הוסיפו את הודעת ה console.log לכל אחת משלושת הקומפוננטות ושימו לב איפה הודעת ההדפסה מופיעה בכל קומפוננטה.
חשבו: למה הודעות ההדפסה שכתבנו בקומפוננטות צד-לקוח מופיעות גם בקונסול של הדפדפן וגם בחלון שורת הפקודה?