Menu

Strict Mode ב-React: למה אפקטים ורינדורים רצים פעמיים

StrictMode הוא עטיפה לזמן פיתוח בלבד שמרנדרת קומפוננטות פעמיים, מריצה setup, cleanup ו-setup של אפקטים ב-mount, ומזהירה על APIs מיושנים, כך שרינדורים לא טהורים ו-cleanups חסרים מתגלים מוקדם. בפרודקשן הוא לא עושה כלום.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

<StrictMode> הוא קומפוננטת React שמפעילה בדיקות נוספות לכל מה שבתוכה, בפיתוח בלבד. הוא מרנדר כל קומפוננטה פעמיים, מריץ ב-mount את ה-setup, ה-cleanup ושוב את ה-setup של כל אפקט, מריץ ref callbacks פעמיים, ומזהיר על APIs מיושנים. זו הסיבה ש-useEffect "רץ פעמיים" בפיתוח. ב-build של פרודקשן הוא לא עושה כלום.

מפעילים אותו על ידי עטיפת האפליקציה, בדרך כלל ב-main.jsx:

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';

createRoot(document.getElementById('root')).render(
    <StrictMode>
        <App />
    </StrictMode>,
);

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

כל Show מוסיף עוד listener, ואף אחד לא מוסר, כך שלחיצה אחת רושמת פעם אחת לכל mount. זו דליפה שמשתמש יכול לגרום לה על ידי פתיחה וסגירה של פאנל. StrictMode היה מראה אותה כבר ב-mount הראשון: בפיתוח הוא עושה mount, unmount ושוב mount, כך שהייתם רואים שתי שורות לוג לכל לחיצה לפני שנגעתם בכפתור.

אפקטים: setup, cleanup, setup

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

התיקון ל-logger הוא cleanup שמסיר את אותה פונקציה שהוא הוסיף:

החליפו כמה שתרצו: כל לחיצה רושמת שורה אחת כל עוד ה-logger מוצג. תחת StrictMode בפיתוח הקונסול היה מתחיל ב-setup, cleanup, setup, והאפליקציה עדיין הייתה מתנהגת נכון. הרצף הזה הוא סימן לאפקט בריא, לא בעיה שצריך לכבות.

אפקטים נפוצים שצריכים cleanup: event listeners, setInterval ו-setTimeout, מנויים, חיבורי WebSocket או צ'אט, ובקשות שצריך להתעלם מהתשובה שלהן אם הקומפוננטה כבר לא קיימת. הדף על useEffect מציג את דפוס ה-cleanup לכל אחד מהם.

רינדורים: נקראים פעמיים

StrictMode גם קורא לפונקציית הקומפוננטה שלכם פעמיים בכל רינדור (וגם לפונקציות שמעבירים ל-useState, useMemo ו-useReducer, ול-constructor, render ו-shouldComponentUpdate של class). React שומרת את התוצאה של קריאה אחת וזורקת את השנייה. קומפוננטה טהורה מחזירה את אותו JSX בשתי הפעמים, כך ששום דבר לא משתנה.

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

כל רינדור מחדש מוסיף רשומה שאף אחד לא ביקש. כאן שמים לב רק אחרי לחיצה. תחת StrictMode הרינדור הראשון כבר היה מציג שתי רשומות חדשות במקום אחת. התיקון הוא לחשב בזמן הרינדור בלי mutation: const shown = [...entries, 'extra'].

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

ref callbacks

מאז React 19, StrictMode מריץ גם ref callbacks פעמיים ב-mount: חיבור, cleanup, חיבור. ref callback שמוסיף listener או רושם את הצומת איפשהו צריך להחזיר cleanup, בדיוק כמו אפקט:

<div
    ref={(node) => {
        const observer = new ResizeObserver(onResize);
        observer.observe(node);
        return () => observer.disconnect();
    }}
/>

אזהרות על APIs מיושנים

StrictMode מזהיר על APIs שבדרך החוצה, בעיקר בקומפוננטות class, כמו מתודות מחזור החיים הישנות UNSAFE_componentWillMount, UNSAFE_componentWillReceiveProps ו-UNSAFE_componentWillUpdate. חלק מה-APIs שהוא נהג להזהיר עליהם, כמו string refs ו-findDOMNode, הוסרו מ-React 19 לגמרי.

הפעלה לחלק מהאפליקציה

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

function App() {
    return (
        <>
            <Header />
            <StrictMode>
                <Sidebar />
                <Content />
            </StrictMode>
            <Footer />
        </>
    );
}

Sidebar ו-Content (וכל מה שהן מרנדרות) מקבלות את הבדיקות; Header ו-Footer לא. אין דרך לכבות אותו לתת עץ בתוך חלק strict.

איפה הוא פעיל כברירת מחדל:

  • Vite: תבנית ה-React עוטפת את <App /> ב-<StrictMode> ב-main.jsx.
  • Next.js: ה-App Router מפעיל אותו כברירת מחדל; reactStrictMode ב-next.config.js שולט בו.

בפרודקשן הוא לא עושה כלום

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

אם אפקט כפול שובר משהו בפיתוח, כמו בקשה שנשלחת פעמיים או אירוע אנליטיקס שנרשם פעמיים, זה מידע. ברוב המקרים התיקון הוא cleanup (דגל ignore לבקשה, ניתוק לחיבור). למשהו שבאמת חייב לקרות פעם אחת בכל טעינה של האפליקציה, כמו אתחול SDK, הריצו אותו מחוץ לכל קומפוננטה ברמת המודול, או הגנו עליו עם דגל ברמת המודול.

let didInit = false;

function App() {
    useEffect(() => {
        if (didInit) return;
        didInit = true;
        initAnalytics(); // runs once per page load, even in StrictMode
    }, []);
}

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

שאלות נפוצות

מה React StrictMode עושה?

בפיתוח בלבד, הוא מרנדר פעמיים כל קומפוננטה שבתוכו, מריץ את ה-setup, ה-cleanup ושוב את ה-setup של כל אפקט כשהקומפוננטה עושה mount, מריץ ref callbacks פעמיים באותה צורה, ומזהיר על APIs מיושנים. הקריאות הנוספות חושפות רינדורים שאינם טהורים ואפקטים עם cleanup חסר.

למה ה-useEffect שלי רץ פעמיים?

כי הקומפוננטה נמצאת בתוך <StrictMode> ואתם מריצים build של פיתוח. React עושה לה mount, מדמה unmount (ה-cleanup שלכם רץ), ועושה mount שוב. אם ה-setup השני גורם לבאג, לאפקט חסר cleanup.

האם StrictMode משפיע על פרודקשן?

לא. build של פרודקשן מרנדר פעם אחת ומריץ כל אפקט פעם אחת, עם <StrictMode> או בלי. אפשר להשאיר אותו בקוד.

איך מכבים את StrictMode?

מסירים את עטיפת ה-<StrictMode>, בדרך כלל ב-main.jsx או ב-index.js, או קובעים reactStrictMode: false ב-next.config.js. בדרך כלל עדיף לתקן את ה-cleanup: ההרצה הכפולה מצביעה על באג שמשתמשים אמיתיים יכולים להיתקל בו.

למה console.log מודפס פעמיים?

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

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל