לדלג לתוכן

סטייט בריאקט מוסבר

אחרי שמבינים קומפוננטות ו-props, השאלה הבאה היא - איך קומפוננטה "זוכרת" דברים ומשתנה בעצמה, לא רק מקבלת נתונים מבחוץ? התשובה היא state, והכלי המרכזי ליצור אותו הוא ההוק useState.

state מול props - ההבדל המהותי

props מגיעים מבחוץ, מקומפוננטת האב, וקומפוננטה לא יכולה לשנות אותם. state הוא ההפך הגמור - הוא שייך לקומפוננטה עצמה, נוצר בתוכה, והיא יכולה לשנות אותו. תחשבו על זה כך - props הם מה שאומרים לקומפוננטה מבחוץ, state הוא מה שהקומפוננטה "זוכרת" בעצמה, כמו האם תפריט פתוח, מה הערך שהוקלד בשדה, או כמה פעמים נלחץ כפתור.

useState - איך זה עובד

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>נלחץ {count} פעמים</p>
      <button onClick={() => setCount(count + 1)}>לחצו כאן</button>
    </div>
  );
}

useState(0) מחזיר מערך עם שני איברים - הערך הנוכחי (count), ופונקציה לעדכן אותו (setCount). ה-0 הוא הערך ההתחלתי, שמשמש רק בפעם הראשונה שהקומפוננטה נטענת.

השם count ו-setCount הם לא קסם - זה פשוט דסטרקצ'רינג של מערך, ואתם יכולים לקרוא לזה איך שתרצו. המוסכמה [value, setValue] היא רק קונבנציה נעימה לקריאה, לא כלל מחייב.

למה קוראים ל-setCount ולא פשוט לשנות את count

זו הנקודה הכי חשובה להבין. אם תנסו לכתוב count = count + 1 ישירות, זה לא יעבוד, וזה לא במקרה. ריאקט צריך "לדעת" שמשהו השתנה כדי להריץ מחדש (re-render) את הקומפוננטה ולעדכן את מה שמוצג על המסך. שינוי ישיר של המשתנה לא "מודיע" לריאקט על כלום. הפונקציה setCount היא בדיוק הדרך התקנית להודיע לריאקט "הערך הזה השתנה, בבקשה תעדכן את הממשק".

// לא עובד - ריאקט לא יודע שמשהו השתנה
count = count + 1;

// עובד - ריאקט מריץ מחדש את הקומפוננטה עם הערך החדש
setCount(count + 1);

עדכון פונקציונלי - כשתלוי בערך הקודם

כשהעדכון תלוי בערך הקודם של ה-state, עדיף להעביר ל-setCount פונקציה במקום ערך ישיר:

setCount((prevCount) => prevCount + 1);

ההבדל נראה זניח, אבל הוא קריטי כשקוראים ל-setCount כמה פעמים ברצף. ריאקט לא בהכרח מעדכן את count באופן מיידי, אז אם קוראים setCount(count + 1) פעמיים ברצף באותה פונקציה, שתי הפעמים עשויות "לראות" את אותו count הישן, ולהתעדכן רק בפעם אחת בפועל במקום שתיים. שימוש בגרסה הפונקציונלית פותר את זה, כי ריאקט מבטיח שהיא תמיד מקבלת את הערך העדכני ביותר.

// עלול לעדכן רק פעם אחת במקום שתיים
setCount(count + 1);
setCount(count + 1);

// מבטיח עדכון פעמיים
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);

state עם אובייקטים ומערכים - אל תשנו ישירות

בדיוק כמו ש-props אסור לשנות ישירות, גם state של אובייקט או מערך אסור לשנות ישירות - צריך תמיד ליצור עותק חדש, בדרך כלל עם ספרד:

const [user, setUser] = useState({ name: "דנה", age: 28 });

// לא נכון - שינוי ישיר, ריאקט לא בהכרח יריץ מחדש
user.age = 29;

// נכון - יצירת אובייקט חדש עם השינוי
setUser({ ...user, age: 29 });

הסיבה היא שריאקט בודק אם צריך לרנדר מחדש בעזרת השוואה מהירה (בדרך כלל ===) בין הערך הישן לחדש. אם שיניתם את האובייקט "במקום", ה-reference שלו נשאר זהה, וריאקט עלול פשוט לא לשים לב שמשהו השתנה בכלל.

כל קומפוננטה מנהלת state משלה

כשקוראים ל-useState בתוך קומפוננטה, כל "מופע" של הקומפוננטה מקבל state נפרד לגמרי, גם אם זו אותה קומפוננטה בדיוק שמופיעה כמה פעמים:

function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

שני ה-Counter בדוגמה הזאת לגמרי עצמאיים - לחיצה על הכפתור באחד לא משפיעה על השני בכלל, כי לכל אחד יש state נפרד משלו.

למה זה השינוי הכי גדול בדרך חשיבה

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

בקורס צד לקוח יש פרק שלם שמתמקד ב-state, עם תרגילים שבונים אינטואיציה על מה קורה מתחת למכסה המנוע בכל קריאה ל-setState.

לסיכום

state הוא הזיכרון הפנימי של קומפוננטה, שאותו היא יכולה לשנות בעצמה, בניגוד ל-props שמגיעים מבחוץ. useState מחזיר ערך נוכחי ופונקציה לעדכון, וחובה לעדכן דרך הפונקציה הזאת, ולעולם לא ישירות, כדי שריאקט יידע לרנדר מחדש. עדכון פונקציונלי ואי שינוי ישיר של אובייקטים ומערכים הם שני הכללים החשובים ביותר לזכור.

יש לכם state שלא מתעדכן כמו שציפיתם? תביאו את הקוד לדיסקורד ונבדוק ביחד.

הצטרפו לקהילה בדיסקורד