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

You will learn

  • איך הגדרת מצב מפעילה עיבוד מחדש
  • מתי וכיצד עדכוני הstate
  • מדוע הstate לא מתעדכנת מיד לאחר הגדרתו
  • איך מטפלי אירועים ניגשים ל”תמונת מצב” של הstate

הגדרת מצב מפעילים מעבד

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

בדוגמה זו, כאשר אתה לוחץ על “שלח”, setIsSent(true) אומר ל-React לעבד מחדש את ממשק המשתמש:

import { useState } from 'react';

export default function Form() {
  const [isSent, setIsSent] = useState(false);
  const [message, setMessage] = useState('Hi!');
  if (isSent) {
    return <h1>Your message is on its way!</h1>
  }
  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      setIsSent(true);
      sendMessage(message);
    }}>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

function sendMessage(message) {
  // ...
}

זה מה שקורה כשאתה לוחץ על הכפתור:

  1. המטפל באירוע ‘onSubmit’ מבצע.
  2. setIsSent(true) מגדיר את isSent ל-true ומעמיד בתור עיבוד חדש.
  3. React מעבד מחדש את הרכיב בהתאם לערך ‘isSent’ החדש.

בואו נסתכל מקרוב על הקשר בין מצב לעיבוד.

העיבוד מצלם תמונת מצב בזמן

“עיבוד” פירושו ש-React קורא לרכיב שלך, שהוא פונקציה. ה-JSX שאתה מחזיר מהפונקציה הזו הוא כמו תמונת מצב של ממשק המשתמש בזמן. הprops, מטפלי האירועים והמשתנים המקומיים חושבו כולם באמצעות מצבו בזמן העיבוד.

שלא כמו תמונה או פריים של סרט, “תמונת הstate” של ממשק המשתמש שאתה מחזיר הוא אינטראקטיבי. זה כולל לוגיקה כמו מטפלי אירועים שמציינים מה קורה בתגובה לקלט. React מעדכנת את המסך כך שיתאים לתמונת מצב זו ומחברת את מטפלי האירועים. כתוצאה מכך, לחיצה על כפתור תפעיל את מטפל הקליקים מה-JSX שלך.

כאשר React מעבד מחדש רכיב:

  1. React קורא לפונקציה שלך שוב.
  2. הפונקציה שלך מחזירה תמונת מצב חדשה של JSX.
  3. React מעדכן את המסך כך שיתאים לתמונת הstate שהפונקציה שלך החזירה.
  1. React executing the function
  2. Calculating the snapshot
  3. Updating the DOM tree

Illustrated by Rachel Lee Nabors

כזיכרון של רכיב, מצב אינו כמו משתנה רגיל שנעלם לאחר החזרה של הפונקציה שלך. הstate למעשה “חיה” ב-React עצמה - כאילו על מדף! - מחוץ לתפקידך. כאשר React קורא לרכיב שלך, הוא נותן לך תמונת מצב של העיבוד המסוים הזה. הרכיב שלך מחזיר תמונת מצב של ממשק המשתמש עם קבוצה חדשה של props ומטפלי אירועים ב-JSX שלו, כולם מחושבים באמצעות ערכי הstate מהעיבוד הזה!

  1. You tell React to update the state
  2. React updates the state value
  3. React passes a snapshot of the state value into the component

Illustrated by Rachel Lee Nabors

הנה ניסוי קטן כדי להראות לך איך זה עובד. בדוגמה זו, אתה עשוי לצפות שלחיצה על כפתור “+3” תגדיל את המונה שלוש פעמים מכיוון שהוא קורא ל’setNumber(מספר + 1)’ שלוש פעמים.

ראה מה קורה כשאתה לוחץ על כפתור “+3”:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

שימו לב ש’מספר’ עולה רק פעם אחת בכל קליק!

מצב ההגדרה משנה את זה רק עבור העיבוד הבא. במהלך העיבוד הראשון, ‘מספר’ היה ‘0’. זו הסיבה, במטפל onClick של *הרינדור הזה, הערך של number עדיין 0 גם לאחר שנקרא setNumber(number + 1):

<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>

הנה מה שמטפל הלחיצה של הכפתור הזה אומר ל-React לעשות:

  1. setNumber(number + 1): number הוא 0 ולכן setNumber(0 + 1).
    • React מתכונן לשנות את ‘מספר’ ל-‘1’ בעיבוד הבא.
  2. setNumber(number + 1): number הוא 0 ולכן setNumber(0 + 1).
    • React מתכונן לשנות את ‘מספר’ ל-‘1’ בעיבוד הבא.
  3. setNumber(number + 1): number הוא 0 ולכן setNumber(0 + 1).
    • React מתכונן לשנות את ‘מספר’ ל-‘1’ בעיבוד הבא.

למרות שקראת ‘setNumber(number + 1)’ שלוש פעמים, במטפל האירועים של העיבוד הזה הוא תמיד ‘0’, אז אתה מגדיר את הstate ל-‘1’ שלוש פעמים. זו הסיבה שאחרי שהמטפל באירועים שלך מסיים, React מעבד מחדש את הרכיב עם ‘מספר’ שווה ל-‘1’ במקום ‘3’.

אתה יכול גם לדמיין זאת על ידי החלפה מנטלית של משתני מצב עם הערכים שלהם בקוד שלך. מכיוון שמשתנה הstate ‘מספר’ הוא ‘0’ עבור העיבוד הזה, מטפל האירועים שלו נראה כך:

<button onClick={() => {
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
}}>+3</button>

עבור העיבוד הבא, מספר הוא 1, כך שמטפל הקליקים של העיבוד הזה נראה כך:

<button onClick={() => {
setNumber(1 + 1);
setNumber(1 + 1);
setNumber(1 + 1);
}}>+3</button>

זו הסיבה שלחיצה נוספת על הכפתור תגדיר את המונה ל-‘2’, ואז ל-‘3’ בלחיצה הבאה, וכן הלאה.

מצב לאורך זמן

ובכן, זה היה כיף. נסה לנחש מה תתריע לחיצה על הכפתור הזה:

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        alert(number);
      }}>+5</button>
    </>
  )
}

אם אתה משתמש בשיטת ההחלפה מקודם, אתה יכול לנחש שההתראה מציגה “0”:

setNumber(0 + 5);
alert(0);

אבל מה אם תכניס טיימר להתראה, אז הוא יופעל רק לאחר שהרכיב יוצג מחדש? האם יהיה כתוב “0” או “5”? יש לנחש!

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )
}

מוּפתָע? אם אתה משתמש בשיטת ההחלפה, אתה יכול לראות את “תמונת הstate” של הstate שהועברה להתראה.

setNumber(0 + 5);
setTimeout(() => {
alert(0);
}, 3000);

ייתכן שstate המאוחסן ב-React השתנה עד למועד הפעלת ההתראה, אבל זה תוכנן באמצעות תמונת מצב של הstate בזמן שהמשתמש קיים איתו אינטראקציה!

ערך של משתנה מצב לעולם לא משתנה בתוך רינדור, גם אם הקוד של מטפל האירועים שלו הוא אסינכרוני. בתוך onClick של העיבוד, הערך של number ממשיך להיות 0 גם לאחר שנקרא setNumber(number + 5). הערך שלו “תוקן” כאשר React “לקח את תמונת הstate” של ממשק המשתמש על ידי קריאה לרכיב שלך.

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

  1. אתה לוחץ על כפתור “שלח”, שולח “שלום” לאליס.
  2. לפני שההשהייה של חמש שניות מסתיימת, אתה משנה את הערך של השדה “To” ל-”Bob”.

מה אתה מצפה שה’התראה’ תציג? האם זה יציג, “אמרת שלום לאליס”? או שהוא יציג, “אמרת שלום לבוב”? עשה ניחוש על סמך מה שאתה יודע, ואז נסה את זה:

import { useState } from 'react';

export default function Form() {
  const [to, setTo] = useState('Alice');
  const [message, setMessage] = useState('Hello');

  function handleSubmit(e) {
    e.preventDefault();
    setTimeout(() => {
      alert(`You said ${message} to ${to}`);
    }, 5000);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        To:{' '}
        <select
          value={to}
          onChange={e => setTo(e.target.value)}>
          <option value="Alice">Alice</option>
          <option value="Bob">Bob</option>
        </select>
      </label>
      <textarea
        placeholder="Message"
        value={message}
        onChange={e => setMessage(e.target.value)}
      />
      <button type="submit">Send</button>
    </form>
  );
}

React שומר על ערכי הstate “קבועים” בתוך מטפלי האירועים של רינדור אחד. אינך צריך לדאוג אם הstate השתנה בזמן שהקוד פועל.

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

Recap

  • הגדרת מצב מבקשת עיבוד חדש.
  • מצב חנויות React מחוץ לרכיב שלך, כאילו על מדף.
  • כאשר אתה קורא ‘useState’, React נותן לך תמונת מצב של הstate עבור העיבוד הזה.
  • משתנים ומטפלי אירועים אינם “שורדים” עיבוד מחדש. לכל עיבוד יש מטפלי אירועים משלו.
  • כל עיבוד (ומתפקד בתוכו) תמיד “יראה” את תמונת הstate של React נתנה לעיבוד זה.
  • אתה יכול להחליף מצב נפשית במטפלי אירועים, בדומה לאופן שבו אתה חושב על ה-JSX המעובד.
  • למטפלי אירועים שנוצרו בעבר יש את ערכי הstate מהעיבוד שבו הם נוצרו.

Challenge 1 of 1:
יישם רמזור

להלן רכיב תאורה של מעבר חציה שמתחלף בעת לחיצה על הכפתור:

import { useState } from 'react';

export default function TrafficLight() {
  const [walk, setWalk] = useState(true);

  function handleClick() {
    setWalk(!walk);
  }

  return (
    <>
      <button onClick={handleClick}>
        Change to {walk ? 'Stop' : 'Walk'}
      </button>
      <h1 style={{
        color: walk ? 'darkgreen' : 'darkred'
      }}>
        {walk ? 'Walk' : 'Stop'}
      </h1>
    </>
  );
}

הוסף ‘התראה’ למטפל בלחיצה. כאשר האור ירוק ואומר “הליכה”, לחיצה על הכפתור אמורה לומר “עצירה היא הבאה”. כאשר האור אדום ואומר “עצור”, לחיצה על הכפתור אמורה לומר “הליכה היא הבאה”.

האם זה משנה אם אתה שם את ‘התראה’ לפני או אחרי שיחת ‘setWalk’?