הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

בואו נכתוב מסוף עם Python, React ו MobX

02/05/2022

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

המשך קריאה

מה זה Tearing ב React ולמה שיהיה לכם אכפת

01/05/2022

גירסה 18 של ריאקט הכניסה לשימוש מנגנון חדש שנקרא Concurrent Mode. אם אתם בונים אפליקציית ריאקט חדשה ב Vite או create-react-app, המנגנון מופעל כברירת מחדל. אם אתם משדרגים אפליקציה ישנה לריאקט 18 אתם תקבלו הודעה שמבקשת מכם להחליף את פקודת ה ReactDOM.render בפקודה בשם ReactDOM.createRoot כדי להפעיל את המנגנון. מנגנון ה Concurrent Mode אמור לעזור לפתור בעיות ביצועים שנובעות מ render-ים ארוכים מדי.

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

את המושג tearing בהקשר של בעיית UI לא מצאתי בתיעוד של ריאקט או בהכרזה על ריאקט 18, אלא רק מפוסטים אחרים שדיברו על הבעיה והרצאה מצוינת ביוטיוב של Daishi Kato. אם יש לכם 20 דקות פנויות שווה להקשיב לו: https://www.youtube.com/watch?v=oPfSC5bQPR8.

בחזרה ל Tearing - בהקשר של ריאקט המושג מתאר מצב בו Concurrent Mode גורם ל UI להיות לא קונסיסטנטי בגלל שינוי משתנים גלובאליים. הבעיה היא כזאת:

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

  2. באמצע שריאקט עושה render לקומפוננטות האלה, פתאום הוא מגלה שיש שינוי יותר דחוף כי מישהו קרא ל startTransition. אז ריאקט עוצר הכל והולך לעשות את ה render היותר דחוף שלו. בואו נגיד שהיו לי 6 קומפוננטות שמושפעות ממשתנה גלובאלי, וריאקט רינדר 3 מהן לפני שהיה צריך לעצור.

  3. תוך כדי ה render היותר דחוף, הערך של המשתנה הגלובאלי משתנה.

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

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

דאישי הראה בהרצאה דוגמה מעניינת, אחרי שצמצמתי אותה קצת הגעתי לקוד הזה שממחיש בדיוק את הבעיה:

import { useState, useTransition } from 'react'

let lastMouseX = 0;

window.addEventListener('mousemove', (e) => {
  lastMouseX = e.offsetX;
});

function MouseTracker() {
  const start = performance.now();
  while (performance.now() - start < 20);

  return (
    <p>{lastMouseX}</p>
  );
}

function App() {
  const [count, setCount] = useState(0)
  const [isPending, startTransition] = useTransition();

  return (
    <div className="App">
      <p>isPending = {JSON.stringify(isPending)}</p>
      <button onClick={() => {
        startTransition(() => {
          setCount(c => c + 1)
        });
      }}>{count}</button>
      <MouseTracker />
      <MouseTracker />
      <MouseTracker />
      <MouseTracker />
      <MouseTracker />
      <MouseTracker />
    </div>
  )
}

export default App

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

בגירסאות ישנות של ריאקט לא משנה מה היינו עושים תמיד היינו מקבלים את אותו ערך של lastMouseX מופיע 6 פעמים. בזמן שריאקט עסוק ב render, הדפדפן לא מפעיל את ה callback שמטפל בטיפול באירוע. רק אחרי שה render מסתיים לדפדפן יש הזדמנות לעדכן את המשתנה הגלובאלי ואנחנו נקבל את הערך החדש בפעם הבאה שנלחץ על הכפתור שמרנדר מחדש את הקומפוננטה.

בגירסא 18 של ריאקט לפני Concurrent Mode, או אם לא קוראים ל startTransition, ריאקט מבצע את ה render "במכה אחת" כמו בגירסאות הישנות, ושוב לדפדפן אין הזדמנות לשנות את הערך של המשתנה הגלובאלי בין רינדור של קומפוננטות.

בגירסא 18 של ריאקט, אם מוסיפים קריאה ל startTransition כמו שהוספתי בדוגמה, הדפדפן עוצר את הרינדור באמצע כדי לטפל בלחיצה על הכפתור. זה נותן לדפדפן הזדמנות לטפל באירוע mousemove באמצע ה render ולעדכן את המשתנה הגלובאלי. התוצאה היא ש-6 המופעים של MouseTracker לא מציגים את אותו ערך.

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

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

מובאקס - מעבר לבייסיקס

29/04/2022

בעבר כתבתי כאן מספר פוסטים על מובאקס וגם העברתי עליו וובינר של שעה, כולם מתאימים בתור היכרות ראשונה עם מובאקס:

  1. מובאקס בעשר דקות

  2. איך לנהל State גלובאלי של יישום ריאקט עם MobX

  3. וובינר מבוא למובאקס

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

המשך קריאה

מדריך קוד: בואו נכתוב משחק זיכרון ב Redux ו React

06/04/2022

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

המשך קריאה

מהו useDeferredValue ולמה שיהיה לכם אכפת

03/04/2022

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

אחד השימושים של הפיצ'ר הזה הוא הפונקציה useDeferredValue שמגדירה לריאקט שחישוב מסוים הוא פחות חשוב ואפשר לחכות קצת עם ה render-ים שתלויים בו כדי לא להאט את כל העמוד.

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

הקוד הבא ממחיש את ההתנהגות הרצויה תוך שימוש ב useDeferredValue:

export default function App() {
  const [search, setSearch] = useState("");
  const dsearch = useDeferredValue(search, { timeoutMs: 10000 });
  return (
    <div className="App">
      <input
        type="search"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
      />
      <FilteredList search={dsearch} isPending={dsearch !== search} />
    </div>
  );
}

ואפשר לראות אותו בפעולה בקודסנדבוקס הבא:

קצת הסברים על הקוד:

  1. הסטייט search מייצג את הטקסט בתיבה; המשתנה dsearch הוא עותק של search שהאלמנטים שתלויים בו פחות חשובים.

  2. בפרק הזמן בין עדכון search לעדכון dsearch ריאקט מנסה ברקע לרנדר את הקומפוננטה FilteredList שמושפעת מ dsearch. בהתחלה הערכים של dsearch ו search יהיו שונים ואחרי שהרינדור האיטי יסתיים הערכים יהיו שווים. אני בודק את ההבדל בין שני המשתנים כדי לדעת אם ריאקט עכשיו עובד ברקע, ומשקף את זה למשתמשים באמצעות המשתנה isPending.

  3. אם תכנסו לקודסנדבוקס ותשחקו שם עם המספרים תגלו שאומנם עבור 10,000 שורות הקוד עובד ממש בסדר, ככל שמעלים את מספר השורות אנחנו מצליחים לשבור את useDeferredValue ולמשל ב 90,000 שורות האיטיות כבר מורגשת בעדכון תיבת הטקסט. אולי זה ייפתר בגירסאות עתידיות של ריאקט ובכל מקרה זו תזכורת טובה לכך שתיקון גנרי בפריימוורק בדרך כלל לא יכול לפתור לכולם את הבעיות, וגם בריאקט 18 נצטרך לשים לב למה אנחנו מרנדרים כדי לשמור על ביצועים טובים.

חידת ריאקט: קומפוננטות, אפקטים וסדר פעולות

09/08/2021

הקוד הבא מתאר שתי קומפוננטות, שתיהן קוראות ל useEffect:

import { useEffect, useState } from "react";

export default function Page(props) {
  useEffect(function () {
    document.title = "Page";
  }, []);

  return <Content />;
}

function Content(props) {
  const [title, setTitle] = useState(document.title);

  useEffect(function () {
    setTitle(document.title);
  }, []);

  return <p>Page title = {title}</p>;
}

והשאלות:

  1. איזה אפקט ירוץ קודם?

  2. מה יהיה תוכן העמוד אחרי רינדור הקומפוננטה Page?

  3. איך אפשר לשנות את סדר האפקטים ולקבל תוצאה שונה על העמוד?

ריאקט, כלים אוטומטיים ו Strict Mode

25/10/2020

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

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

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

    console.count("Counter::render");

    function handleClick() {
        setCount(c => c + 1);
        setCount(c => c + 1);
    }

    return <button onClick={handleClick}>Clicks: {count}</button>;
}

התוצאה בקונסול צריכה להיות הדפסה פעם אחת של הטקסט Counter::render, למרות שקראנו ל setCount פעמיים. זה קורה בגלל ש React מחכה עם העדכון עד שכל פקודות ה set ייגמרו. אבל אם הסיפור היה נגמר כאן לא היה מה להכין דוגמה עליו; מה שמעניין בריאקט זה שאם הקוד היה נקרא כתוצאה מ setTimeout במקום כתוצאה מלחיצה על כפתור, אז ריאקט כבר כן היה קורא ל render פעמיים במקום לאחד את ה set-ים (התנהגות מתועדת ועם הבטחה לאי תאימות בעתיד - כלומר אל תבנו על זה שזה יישאר ככה).

אבל אנחנו לא כאן בשביל לדבר על ריאקט אלא על הדוגמה שלי שלא עבדה. לא משנה כמה התאמצתי הקומפוננטה שלי הדפיסה על המסך Counter::render פעמיים!. בדיוק כשחשבתי לוותר הלכתי להעיף מבט בקוד של index.js שנוצר אוטומטית עם create-react-app:

  ReactDOM.render(
     <React.StrictMode>
       {app}
     </React.StrictMode>,
    document.getElementById('root')
  );

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

StrictMode renders components twice (on dev but not production) in order to detect any problems with your code and warn you about them (which can be quite useful).

הלקח החשוב כאן אם גם אתם מלמדים תכנות - תמיד תבדקו את הדוגמאות שלכם בבית לפני שמראים לכיתה (גם אם הראיתם את הדוגמה הזאת לכיתות אחרות עשרות פעמים). אף פעם אי אפשר לדעת מי יזיז לכם את הגבינה.

איך להוסיף תמיכה ב react-testing-library לכל פרויקט וובפאק

18/09/2020

לא חייבים להשתמש ב create-react-app רק בשביל לקבל תמיכה בבדיקות אוטומטיות ב React, ולמעשה זה די פשוט להתקין את jest ואיתו את התמיכה ב react-testing-library כמעט לכל פרויקט וובפאק. נכון יש כמה מגבלות אבל ברוב המקרים הצעדים בפוסט כאן מספיקים. בואו נצא לדרך.

המשך קריאה

שלום GraphQL חלק 2: חיבור אפליקציית ריאקט עם Relay

14/09/2020

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

בפוסט זה אציג דוגמת קוד עובדת של React ו Relay שקוראת מידע מה Github API וכך תוכלו לראות גם איך החלקים של GraphQL ו React מתחברים יחד וגם לקבל נקודת התחלה לפרויקט שלכם שישתמש בכלים אלה.

המשך קריאה

שלום GraphQL

13/09/2020

כבר שנים שהבחירה הפופולרית של מפתחי APIs שצריכים להחליט על מבנה הממשק נקראת REST. המעבר ל REST התרחש בצורה הדרגתית והחליף ממשקים מבוססי XML שקדמו לו והוא בא יחד עם המעבר להעברת מידע באמצעות JSON. אנחנו אוהבים REST כי מאוד פשוט להבין ממשקי תכנות שבנויים כך, אבל ל REST יש בעיה רצינית ככל שהממשקים גדלים וצריך להפריד בין קוד השרת לקוד הלקוח.

בפוסט זה נדבר על הבעיות של REST, על GraphQL ונראה דוגמאות קוד ראשונות לעבודה עם GraphQL.

המשך קריאה