שמירה על טהרת הרכיבים

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

You will learn

  • מהי טוהר וכיצד זה עוזר לך להימנע מבאגים
  • איך לשמור על רכיבים טהורים על ידי שמירת שינויים משלב העיבוד
  • איך use מצב קפדני כדי למצוא טעויות ברכיבים שלך

טוהר: רכיבים כנוסחאות

במדעי המחשב (ובמיוחד בעולם התכנות הפונקציונלי), פונקציה טהורה היא פונקציה בעלת המאפיינים הבאים:

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

אולי אתה כבר מכיר דוגמה אחת לפונקציות טהורות: נוסחאות במתמטיקה.

שקול את הנוסחה המתמטית הזו: y = 2x.

אם x = 2 אז y = 4. תָמִיד.

אם x = 3 אז y = 6. תָמִיד.

אם x = 3, y לפעמים לא יהיה 9 או –1 או 2.5 בהתאם לשעה ביום או ל-state של שוק המניות.

אם y = 2x ו-x = 3, y יהיה תמיד 6.

אם נהפוך את זה לפונקציה JavaScript, זה ייראה כך:

function double(number) {
return 2 * number;
}

בדוגמה שלמעלה, double היא פונקציה טהורה. אם תעביר אותה 3, היא תחזיר 6. תָמִיד.

React עוצב סביב הרעיון הזה. React מניח שכל רכיב שאתה כותב הוא פונקציה טהורה. זה אומר שרכיבי React שאתה כותב חייבים תמיד להחזיר את אותו JSX בהינתן אותן כניסות:

function Recipe({ drinkers }) {
  return (
    <ol>    
      <li>Boil {drinkers} cups of water.</li>
      <li>Add {drinkers} spoons of tea and {0.5 * drinkers} spoons of spice.</li>
      <li>Add {0.5 * drinkers} cups of milk to boil and sugar to taste.</li>
    </ol>
  );
}

export default function App() {
  return (
    <section>
      <h1>Spiced Chai Recipe</h1>
      <h2>For two</h2>
      <Recipe drinkers={2} />
      <h2>For a gathering</h2>
      <Recipe drinkers={4} />
    </section>
  );
}

כאשר תעביר את drinkers={2} ל-Recipe, הוא יחזיר את JSX המכיל 2 cups of water. תָמִיד.

אם תעבור את drinkers={4}, הוא יחזיר את JSX המכיל 4 cups of water. תָמִיד.

ממש כמו נוסחה מתמטית.

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

A tea recipe for x people: take x cups of water, add x spoons of tea and 0.5x spoons of spices, and 0.5x cups of milk

Illustrated by Rachel Lee Nabors

תופעות לוואי: השלכות (לא) מתוכננות

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

הנה רכיב ששובר את הכלל הזה:

let guest = 0;

function Cup() {
  // Bad: changing a preexisting variable!
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup />
      <Cup />
      <Cup />
    </>
  );
}

רכיב זה קורא וכותב משתנה guest המוצהר מחוץ לו. זה אומר שקריאה לרכיב זה מספר פעמים תפיק JSX שונה! ויותר מכך, אם שאר רכיבים יקראו guest, הם יפיקו גם JSX שונה, בהתאם למועד העיבוד שלהם! זה לא צפוי.

אם נחזור לנוסחה שלנו y = 2x, כעת גם אם x = 2, איננו יכולים לסמוך על כך שy = 4. הבדיקות שלנו עלולות להיכשל, ה-users שלנו יהיו מבולבלים, מטוסים ייפלו מהשמיים - אתם יכולים לראות איך זה יוביל לבאגים מבלבלים!

אתה יכול לתקן את הרכיב הזה על ידי העברת guest כאביזר במקום:

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaSet() {
  return (
    <>
      <Cup guest={1} />
      <Cup guest={2} />
      <Cup guest={3} />
    </>
  );
}

כעת הרכיב שלך טהור, מכיוון שה-JSX שהוא מחזיר תלוי רק באביזר guest.

באופן כללי, אין לצפות שהרכיבים שלך יוצגו בסדר מסוים. זה לא משנה אם אתה קורא y = 2x לפני או אחרי y = 5x: שתי הנוסחאות ייפתרו ללא תלות זו בזו. באותו אופן, כל רכיב צריך רק “לחשוב על עצמו”, ולא לנסות לתאם או להיות תלוי באחרים במהלך העיבוד. עיבוד הוא כמו בחינה בבית הספר: כל רכיב צריך לחשב JSX בעצמו!

Deep Dive

זיהוי חישובים לא טהורים עם StrictMode

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

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

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

שימו לב כיצד הדוגמה המקורית הציגה “אורח מס’ 2”, “אורח מס’ 4” ו”אורח מס’ 6” במקום “אורח מס’ 1”, “אורח מס’ 2” ו”אורח מס’ 3”. הפונקציה המקורית הייתה לא טהורה, אז הקריאה לה פעמיים שברה אותה. אבל הגרסה הטהורה הקבועה עובדת גם אם הפונקציה נקראת פעמיים בכל פעם. פונקציות טהורות מחושבות רק, כך שקריאה להן פעמיים לא תשנה שום דבר—בדיוק כמו שקריאה ל-double(2) פעמיים לא משנה את מה שמוחזר, ופתרון y = 2x פעמיים לא משנה מה זה y. אותן כניסות, אותן יציאות. תָמִיד.

למצב קפדני אין השפעה בייצור, כך שהוא לא יאט את האפליקציה עבור ה-users שלכם. כדי להצטרף למצב קפדני, אתה יכול לעטוף את רכיב השורש שלך לתוך <React.StrictMode>. מסגרות מסוימות עושות זאת כברירת מחדל.

מוטציה מקומית: הסוד הקטן של הרכיב שלך

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

עם זאת, זה בסדר גמור לשנות משתנים ואובייקטים שיצרת רק תוך כדי רינדור. בדוגמה זו, אתה יוצר מערך [], מקצה אותו למשתנה cups ולאחר מכן push תריסר כוסות לתוכו:

function Cup({ guest }) {
  return <h2>Tea cup for guest #{guest}</h2>;
}

export default function TeaGathering() {
  let cups = [];
  for (let i = 1; i <= 12; i++) {
    cups.push(<Cup key={i} guest={i} />);
  }
  return cups;
}

אם המשתנה cups או מערך [] נוצרו מחוץ לפונקציה TeaGathering, זו תהיה בעיה ענקית! היית משנה אובייקט קיים על ידי דחיפת פריטים למערך הזה.

עם זאת, זה בסדר כי use יצרת אותם במהלך אותו עיבוד, בתוך TeaGathering. אף קוד מחוץ ל-TeaGathering לא יידע לעולם שזה קרה. זה נקרא “מוטציה מקומית” - זה כמו הסוד הקטן של הרכיב שלך.

איפה אתה יכול לתתuse תופעות לוואי

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

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

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

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

Deep Dive

למה ל-React אכפת מהטוהר?

כתיבת פונקציות טהורות דורשת קצת הרגל ומשמעת. אבל זה גם פותח הזדמנויות נפלאות:

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

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

Recap

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

Challenge 1 of 3:
תקן שעון מקולקל

רכיב זה מנסה להגדיר את המחלקה CSS של <h1> ל-"night" במהלך הזמן מחצות עד שש שעות בבוקר, ו"day" בכל הזמנים האחרים. עם זאת, זה לא עובד. האם אתה יכול לתקן את הרכיב הזה?

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

export default function Clock({ time }) {
  let hours = time.getHours();
  if (hours >= 0 && hours <= 6) {
    document.getElementById('time').className = 'night';
  } else {
    document.getElementById('time').className = 'day';
  }
  return (
    <h1 id="time">
      {time.toLocaleTimeString()}
    </h1>
  );
}