ריאקט - הבסיס
vite
כלי לבניית תכנית ווב בצורה מהירה:
ניתן להשתמש במנגנון ה-template create כדי ליצור פרוייקטי ריאקט בלי מאמץ.
מריץ גם את השרת.
מה זה ריאקט?
זהו פריימווק דקלרטיבי לממשק משתמש.
מה זה קומפוננטה
הגדרה עצמאית שניתן להשתמש בה באופן חוזר לרכיב בממשק משתמש.
ליצור קומפוננטה
החזרה עם סוגריים מעוגלות שאומר שזה קוד HTML בתוך קוד JS.
1 | function Header() { |
אפשר להשתמש בזה באופן הבא:
1 | function App() { |
Build in comps
lowercase
html elements
dom nodes
Custom comps:
uppercase
defined by u - wraps built in
react traverse until it recognizes only built in comps
ערכים דינמיים בקומפוננטה
כדי להוסיף ערכים יש להשתמש ב-{}.
1 | return ( |
ערכים דינמיים לתכונות HTML
ניתן לעשות import לקבצי css וקבצים אחרים.
1 | import reactImg from "./assets/react-core-concepts.png"; |
העברת מידע בעזרת props
1 | function CoreConcept(props) { |
או בעזרת desctruction ניתן להשתמש באובייקטים ואז לפצל אותם לתכונות:
1 | function CoreConcept({ image, title, description }) { |
פיצול קומפוננטה לקובץ
בקובץ אחר נרצה להוסיף export default במידה וזה הדבר היחידי בקובץ:
1 | export default function CoreConcept({ image, title, description }) { |
ולעדכן imports.
import XXX.css
כאשר מייבאים קבצי CSS יש לשים לב שכל האלמנטים ייקבלו את העיצוב גם אם אנחנו נרצה את זה רק לקומפוננטה אחת.
props children
ניתן להעביר ערכים גם בעזרת הילדים:
1 | function MyComp(props){ |
השימוש בילדים זה אופציה מהירה יותר ומאפשר אנקפסולציה לקוד JSX.
כמו כן גם מאפשר כתיבה שדומה יותר לקוד HTML.
אם צריך לשלוט בצורה טובה יותר במידע שעובר אז שימוש בתכונות ספציפיות יותר טוב.
Component listeners
ניתן להעביר פונקציות על מנת להפעיל התנהגות:
1 | export default function TabButton({ children, onClick }) { |
ניתן להשתמש בפוקנציות חצים כדי לקנפג קריאה אחרת
1 | export default function TabButton({ children }) { |
מומלץ להחזיר callable לאירוע.
לעדכן את ריאקט שה-UI שלנו השתנה
אם סתם נזרוק משתנה לתוך הקוד, ריאקט לא יידע שהוא ישתנה:
1 | let myName = "Shrek": |
הקומפוננטה תרוץ רק פעם אחת!
כדי לתקן את זה צריך להשתמש במשהו שנקרא React hook.
מה שנשתמש בו הוא useState
1 | import { useState } from "react"; |
חייבים לקרוא לפונקציה ברמה בגבוה
- חייבים להשתמש בזה בתוך קומפוננטה ולא מחוץ.
- חייבים לקרוא לזה בהתחלה ולא בתוך תנאי או משהו פנימי
למשל זה אסור:
1 | function App(){ |
עדכון של הUI
1 | let myName = "Shrek": |
אם לרנדר או לא לרנדר - תנאים
אפשר לקצר עם ?: או עם &&.
דרך ראשונה - ?:
1 | {!selectedTopic ? <p>Please select an example.</p> : null} |
דרך שנייה - &&
1 | {selectedTopic && ( |
דרך שלישית - if else
1 | let tableContent = <p>Please select a topic</p>; |
Styling using classes
בתוך JSX אנחנו משתמשים ב-className.
1 | <button className={isSelected ? 'active' : undefined}>Click me </button> |
ניתן להשתמש בכמה קלאסים
1 | <button className="btn btn-primary">Click</button> |
Listing
בקצרה השתמשו ב-map כדי ליצור ממערך רשימה של קומפוננטות.
1 | {CORE_CONCEPTS.map((core) => ( |
השתמשו ב-key כדי לומר ל-react איך ל
React - Deep dive
ריאקט עובד בתצורת קומפילציה - הוא בונה ומאפטמז את הקוד ומייצר קבצים שניתן להריץ בדפדפן.
להשתמש בפונקציות createElement
האם חייבים JSX?
ניתן גם לייצר אלמנטים בעזרת פונקציות בנויות של ריאקט.
1 | React.createElement( |
==
1 | <div id="content"> |
בתצורה של הפונקציות לא צריך באמת לבנות קבצי JSX.
JSX הוא בסופו של דבר תוסף שיש לו תהליך בנייה משלו.
בעזרת קריאה ישירה לפנוקציית ג’אווהסקריפטית אז נמנע מתהליך הבנייה.
Fragments
אלמנטים JSX צריכים שיהיה להם אלמנט שורש:
אסור:
1 | <Header></Header> |
חייבים:
1 | <div> |
הסיבה העיקרת לכך שאי אפשר להחזיר 2 ערכים אלה רק ערך אחד לאלמנט.
שחייבים להחזיר אלמנט אחד לכל אלמנט ב-JSX.
במקום שיהיה לנו div נוסף נוכל להשתמש באלמנט Fragment.
1 | import Fragment from 'react'; |
תחביר חדש מאפשר לכתוב פרגמנטים בצורה ריקה
1 | <> |
פיצול קומפוננטות
קומפוננטות עוקבות אחרי SRP,
כדי שכל קומפוננטה תתעסק במשהו אחד.
הסיבה השנייה היא כדי שקומפוננטות שמשתנות לא ייצטרכו להריץ את כל הקוד מחדש.
תתארו לכם שאם האפליקציה שלכם היא קומפוננטה אחת, כל האפליקציה תתעדכן בעדכון משהו בודד.
פיצול קומפוננטות ע”פ פיצ’רים או סטייט
יתרונות בפיצול קומפוננטות:
- ארגון יותר טוב של הקומפוננטות והלוגיקה.
- ניתן לנהל את הסטייט בצורה יותר חלקה
- ניתן לייבא ולייצא אלמנטים בצורה קלה ומודולרית
- אפליקציה נקייה
תכונות של אלמנטים לא עוברים ישירות לקומפוננטה
המאפיין id לא עובר לתוך האלמנט, הרנדרר מתעלם מהמפאיינים האלו.
1 | <Section id="examples"> |
זה לא עובר לתוך האלמנט section:
1 | function Section({title,chidlren}){ |
כדי להעביר אותם צריך להעביר אותם ידנית:
1 | <Section id="examples"> |
זה לא עובר לתוך האלמנט section:
1 | function Section({title,id, chidlren}){ |
Forwarding props
בעזרת פיצ’ר של ג’אווה סקריפט שנקרא Rest Properties נוכל להעביר כל מיני פרמטרים ישירות בקלות.
ואז נצטרך לבצע Deconstruction על האלמנט:
1 | function Section({title,chidlren, ...props}){ |
Using JSX Slots - Multiple slots
כבר למדנו שאפשר להעביר ילדים לקומפוננטה, זה מאפשר לנו לרנדר אותם בתוך האלמנט.
אולם אם אנחנו צריכים להעביר יותר אלמנטים מאשר ילדים?
לזה קוראים Multiple Slots:
1 | <Card |
בניית קומפוננטות דינמיות על ידי העברת הטייפ
ניתן להעביר את הטייפ של קומפוננטה או אלמנט על מנת ליצור קומפוננטה חדשה ממנה
בשביל אלמנט שלנו נצטרך שזה יתחיל באות גדולה למשל - MyTitle.
אם נרצה להעביר אלמנט קיים נצטרך להעביר מחרוזת כמו "h2".
1 | function MyTitle({chidlren}){ |
יצירת דיפולטים למאפייני קומפוננטה
לעיתים נרצה שקומפוננטה תהיה עם ערכים בסיסיים בברירת מחדל.
ואחר כך אם המשתמש יירצה, לערוך אותה עבורו.
אין פה שום קסם, זה רק פיצ’ר של ג’אווהסקריפט כדי להתחל את זה בצורה פושטה
1 | export default function RedTitle({ titleType = "h1", children }) { |
קומפוננטות הן עצמאיות
כל קומפוננטה היא עצמאית ומבודדת משאר הקומפוננטות, לכן אנו יכולים ליצור הרבה מופעים של אותה קומפוננטה:
1 | <RedTitle>One</RedTitle> |
שינוי סטייט על פי הסטייט הקודם
כאשר אנו משנים את הסטייט של הקומפוננטה והיא תלויה בסטייט הקודם ולא סטייט לגמרי חדש,
אנו צריכים להעביר פונקציה
כן:
1 | setIsEditing(wasEditing => !wasEditing); |
לא:
1 | setIsEditing(!isEditing); |
זה מבטיח שהסטייט האחרון ייקרא בהצלחה ולא יהיה לנו בעיה מבחינת הסטייט האחרון.
הסיבה לכך היא שהמנגנון של ריאקט לא מעדכן ישר את הגרפיקה אלה מתזמן את זה לעתיד.
העתקת אובייקט עדיפה על שינוי הסטייט
כן:
1 | const updateUser = {...user}; |
לא:
1 | const updateUser = {user}; |
מכיוון שהאובייקט זה רפרנס, אנו בטעות משנים את הסטייט השמור - שיכול לגרום לבאגים.
העלאת סטייט לקומפוננטה העליונה
כאשר רוצים לשתף סטייט בין קומפוננטות או כמה קופוננטות צריכות לנהל סטייט משותף הדרך הפשוטה היא לעלות אותו למעלה.
1 | function FirstComp({setName}){ |
שימוש בסטייט יחיד
העקרון בריאקט הוא לנסות לבודד סטייט ולהשתמש בסטייט אחד כדי לייצג את מצב האפליקציה.
למשל במקום לשמור שני סטייטים ללוח משחק ותור של השחקן נוכל לייצג את זה בסטייט אחד - מהלך:
1 | const [player, setPlayer] = useState(); // Who |
1 | const [turns, setTurns] = useState(); // Who and Where |
זה לא בהכרח שומר על עקרון האחריות היחידה אולם במצב הזה אנו נמנעים מלבדוק כמה סטייטים שיכול להיות מצב בעייתי.
Event sourcing Pattern
הדרך האמצעית לניהול סטייט הוא בעזרת Event Sourcing
כמו בדוגמא הקודמת במקום לשמור לוח משחק - אנו שומרים את כל המהלכים ובכל צד אנו יכולים לחשב את המצב הנוכחי.
זהו עקרון ביניים שעובד טוב עם Redux מאחר וזהו הקונספט גם שם.
סטיילינג של קומפוננטות ב-CSS
- CSS גלובלי (.css + className)
יתרונות
הכי פשוט ומהיר להתחלה.
קבצים קטנים, בלי תלות בספריות.
עובד מעולה עם כל בנדלר/SSG.
חסרונות
סיכוי ל־name collisions ולדליפות סגנון.
קושי בניקוי ועקיבות בפרויקטים גדולים.
theming והרכבה מותנית בכללים ידניים.
- CSS Modules
יתרונות
בידוד שמות אוטומטי (scoped).
DX טובה בלי ריצה בזמן אמת.
תמיכה נוחה ב-TypeScript להשלמת שמות מחלקות.
חסרונות
קשה לשתף לוגיקה דינמית (מצבים/var-ים) בלי מחלקות נוספות.
theming מורכב יותר (לרוב עם CSS Variables).
- Sass/SCSS (עם גלובלי או Modules)
יתרונות
משתנים, mixins ו-nesting משפרים ארגון ו-DRY.
קומפייל לפרונט — ללא עלות ריצה.
חסרונות
עוד שכבת כלים/בניית קובץ.
משתני Sass הם סטטיים בזמן build (לא דינמיים בזמן ריצה).
Inline Styles (style={{ ... }})
יתרונות
הצמדה ישירה ללוגיקה; קל לתנאים ודינמיות.
אין התנגשות שמות.
חסרונות
אין pseudo-selectors/מדיות/animations (אלא אם JS).
ביצועים פחות טובים ברינדור חוזר של אובייקטים.
קוד פחות קריא בעיצובים גדולים.
- CSS-in-JS בזמן ריצה (styled-components / Emotion)
יתרונות
סגנון צמוד לקומפוננטה, תחזוקה נוחה.
theming מצוין (ThemeProvider), props דינמיים.
אפשרויות קומפוזיציה חזקות.
חסרונות
עלות ריצה (runtime) והזרקת <style> דינמית.
SSR דורש הגדרה (ServerStyleSheet/extractCritical).
לפעמים איטרציה איטית בקנה מידה גדול.
- CSS-in-JS ללא ריצה (vanilla-extract, Linaria, compiled)
יתרונות
מייצר CSS בזמן build — אפס עלות ריצה.
עדיין כותבים “כמו JS/TS” עם טיפוסיות וכללים מודולריים.
טוב ל-SSR וביצועים.
חסרונות
פחות דינמיות בזמן ריצה לעומת runtime CSS-in-JS.
עקומת לימוד קלה לכלי/קונפיגורציה.
- Utility-First (Tailwind CSS)
יתרונות
פיתוח מהיר מאוד, אין “קפיצה” בין קובצי CSS ו-JSX.
Purge/treeshake חזק → CSS קטן בפרודקשן.
עיצוב עקבי באמצעות design tokens וקונפיגורציה אחת.
חסרונות
JSX “עמוס” בקלאסים; דורש הטמעה של naming mindset.
התאמות מיוחדות (מצבים מורכבים/animations) לפעמים פחות אינטואיטיביות.
תלות במסגרת חיצונית.
- System Props / sx (MUI/Chakra)
יתרונות
API דקלרטיבי ל-spacing/typography/layout.
theming עמוק “מהקופסה”.
מהיר ל-CRUD UI ולמערכות עיצוב.
חסרונות
תלות בספריית UI כבדה יחסית.
לעתים קוד JSX מרובה props לעיצוב.
- CSS Variables (Design Tokens)
יתרונות
theming בזמן ריצה (כולל dark mode) בלי JS כבד.
עובד עם כל שיטה לעיל (גלובלי/Modules/Tailwind).
משתלב נהדר עם SSR ו-static.
חסרונות
צריך ארגון טוב של tokens ושכבות (root/scope).
לוגיקה מתקדמת עדיין ב-JS (toggle/contexts).
מתי לבחור מה (כללי אצבע)
פרויקט קטן/בלוג/דוקו: CSS Modules או גלובלי + קצת Variables.
אפליקציה גדולה עם הרבה מצבים ותמות: CSS-in-JS (Emotion/styled) או Zero-runtime (vanilla-extract) + Tokens.
פיתוח מהיר של UI עקבי: Tailwind או System Props (MUI/Chakra).
ביצועים/SSR קפדניים: Zero-runtime או CSS Modules + CSS Variables ל-theming.
Strict Mode
קומפוננטה המאפשרת למצוא באגים ביותר קלות, למשל זה מריץ את הכל פעמיים - ככה שאם הכל בסדר, התצוגה תהיה בסדר.
1 |
|
עוד:
https://react.dev/reference/react/StrictMode
Refs
מאפשר לעבוד עם האלמנט דום של העמוד.
- גישה לאלמנטים עם סטייט
- מאפשר להוציא פונקציות מחוץ לקומפוננטה
1 | const inputName = useRef(); |
ניגש לערך ע”י קריאה:
1 | inputName.current.value |
הref יעודכן רק אחרי רנדור
והוא לא ייעדכן את הרנדור, רק סטייט יעדכן רנדור.
זאת אומרת שאם שינינו ערך בעזרת ref ה-UI לא יעודכן.
1 | <h2>Welcome {inputName.current ? inputName.current.value : "unknown entity"}</h2> |
State vs Refs
סטייט
שינוי של הסטייט ייעדכן את הגרפיקה של הקומפוננטה.
כדאי להשתמש כאשר הערך נמצא בממשק הגרפי.
לא להשתמש בסטייט לערכים מאחורי הקלעים.
Refs
לא יוצר עדכונים לגרפיקה.
ערך ישיר לאלמנט דום.
כדאי להשתמש ב-ref כאשר רוצים ערכים שלא מעדכנים את הממשק הגרפי בהכרח.
כמו כן ניתן להעביר אותם גם לקומפוננטות אחרות:
1 | const dialogRef = useRef(); |
Forward refs
קומפוננטה ישנה שהעבירה ref אבל בגרסאות חדשות לא צריך.
1 | const FancyButton = React.forwardRef((props, ref) => ( <button ref={ref} className="FancyButton"> {props.children} |
useImperativeHandle
הוק המאפשר העברה של פרטים אחרים כגון פונקציות על מנת למסך את הדיאלוג המקורי.
1 | export default function ResultModal({ ref, result, targetTime }) { |
בקריאה הזו החלפנו את ה-ref וקישרנו אותו ל-ref פנימי.
וככה אפשרנו גם מתודה חדשה שמבחוץ ניתן לקרוא לקומפוננטה בלי קשר ל -ref שמעבירים.
זה יוצר קומפוננטה יותר עצמאית.
קריאה:
1 | ref.current.open(); |
Portals
פורטלים מאפשר להזיז אלמנט לאלמנט אחר בדום.
במודאלים ודיאלוגים זה מושלם כי אנחנו יכולים ליצור אותם תחת אלמנט בודד ב-body ולא באמצע ה-dom.
https://react.dev/reference/react-dom/createPortal
למשל יצירה של אלמנט ב-body:
1 | import { createPortal } from 'react-dom'; |
ניהול סטייט מורכב יותר - העברת פרופ - Prop Drilling
הדרך הנאיבים לנהל סטייט שעובר הרבה קומפוננטות זה פשוט להעביר אותו למטה למטה:
App
- Header
- CartModal
- Cart - Display Cart
- CartModal
- Shop - Update Cart
- Product - Update Cart
קומפוזיציית קומפוננטות
דרך אחת לפתור את הבעיה זה לעלות את הקומפוננטות רמה אחת למעלה
למשל אם הקומפוננטה Product מרונדרת תחת Shop,
נוכל להעביר את הקומפוננטות של Product כ-children המרונדרים ב-App במקום ב-Shop.
App -
- Shop
- Children - Product - has Update cart
1 | <Shop> |
ב-Shop:
1 | <ul id="products">{children}</ul> |
React Context API
Context המועבר בין קומפוננטות, ניתן להשתמש בזה כדי להעביר את הסטייט.
ניצור קונטקס חדש, בדרך כלל נשמור בתיקייה אחרת כגון store:
1 | import { createContext } from "react"; |
כדי להשתמש בו:
1 | import CartContext from './store/shopping-cart-context.jsx' |
בקומפוננטות אחרות ניתן לעשות:
1 | import {use, useContext} from 'react'; |
ההבדל ש-use הוא גמיש יותר כמו להשתמש בו בתוך תנאי.
כמו כן ה-use הוא בריאקט חדש יותר.
Context.Consumer
השתמשנו ב-Provider, אז אפשר גם לעשות Consume בעזרת התגים:
1 | <CartContext.Consumer> |
Context Value השתנה
כשה-Value השתנה ריאקט מעדכן את הקומפוננטה לבד אם
Context separate components
מאוד נוח להוציא את הקונטקס לקומפוננטה משל עצמה
1 | import { createContext } from "react"; |
Use Reducer
הפונקציה מגדירה מעיין סטייט נוסף אולם הוא עובד ע”פ עקרון האיננוטים.
הוא יוצר סטייט ופונקצ’ דיספטצ’.
הפונקציה הזו “זורקת” איוונטים
שלב ראשון נגדיר את ה-Reducer שלנו:
1 | function shoppingCartReducer(state, action) { |
שלב שני נגדיר את הסטייט בעזרת useReducer.
הפרמטר הראשון זה ה-Reducer והשני זה סטייט ראשוני.
1 | const [shoppingCartState, shoppingCartDispatch] = useReducer( |
שלב שלישי עושים דיספטצ’:
1 | function handleAddItemToCart(id) { |
Reducer and Redux
העקרון של רדיוסרים הוא פחות או יותר אותו עקרון של רידקס רק פשוט יותר במהותו.
הוא משלב גם איוונט-טייפ וגם פונק’ רדיוס שמאחדות סטייט רב לסטייט אחד.