مشکل اصلی useEffect: همون باگ با ماسکهای مختلف
خلاصهٔ کاملتر
نویسنده میگه useEffect همون هوکیه که همه سراغش میرن و بیشتر از همه هم گازشون میگیره. به ظاهر بیخطره — یه کد بعد از رندر اجرا کن — ولی همون خط میتونه تب رو فریز کنه، یه درخواست رو دوباره بفرسته یا بیصدا تا کرشکردن صفحه حلقه بزنه. به گفتهی نویسنده، تقریباً همهی باگهای useEffect یه باگه با ماسکهای مختلف: افکت بیشتر از اون چیزی که فکر میکنی اجرا میشه.
بدترین نسخه، حلقهی رندره. تغییر state یه رندر دوباره میسازه، افکت بعد از رندر اجرا میشه، افکت state رو ست میکنه و این چرخه تا کرش ادامه پیدا میکنه. چیزی که جلوش رو میگیره آرایهی وابستگیه؛ آرگومان دومی که به React میگه کِی افکت رو دوباره اجرا کنه. آرایهی خالی یعنی «فقط یه بار، موقع mount»:
useEffect(() => {
setCount(1);
}, []);نسخهی موذیانهترش از code review هم رد میشه، چون آرایهی وابستگی داره و باز هم حلقه میزنه. مثالش یه آبجکت filters هست که تو هر رندر از نو ساخته میشه:
const filters = { query, sort: "recent" };
useEffect(() => {
search(filters).then(setResults);
}, [filters]);به گفتهی نویسنده، React وابستگیها رو با مرجع (reference) مقایسه میکنه نه با محتوا، و آبجکتها، آرایهها و توابع تو هر رندر یه مرجع تازه میگیرن. پس حتی وقتی هیچی عوض نشده، React یه آبجکت متفاوت میبینه و افکت رو دوباره اجرا میکنه؛ یعنی { query: "react" } === { query: "react" } نتیجهش false میشه. محتوا یکیه ولی هویت فرق میکنه و React فقط هویت رو چک میکنه.
نویسنده دو راهحل میده. اول پایدارکردن مرجع با useMemo تا آبجکت فقط وقتی دادهی واقعی عوض شد تغییر کنه. دوم، بیخیال آبجکت شدن و وابستهشدن مستقیم به مقدار اولیه (primitive) مثل query که React اون رو با محتوا مقایسه میکنه. همین تله برای آرایهها و توابع هم هست؛ برای توابع، useCallback معادل useMemo به حساب میاد.
نویسنده پیشنهاد میکنه قبل از اینکه باگ به production برسه، قانون ESLint به اسم react-hooks/exhaustive-deps رو روشن کنی تا وابستگیهای جاافتاده یا ناپایدار رو همون تو ادیتور علامت بزنه. ابزار React Doctor هم همین کلاس باگ رو فراتر از وابستگیهای جاافتاده پیدا میکنه و موارد دیگه مثل نبود cleanup و زنجیرههای افکت رو علامت میزنه.
درس اصلی به گفتهی نویسنده اینه که آرایهی وابستگی یه فرمالیته برای ساکتکردن هشدار نیست، بلکه کل قراردادیه که به React میگه افکتت کِی اجازهی اجرا داره. اشتباهش با آرایهی جاافتاده تو رو حلقه میاندازه و اشتباهش با مرجع ناپایدار، حلقهی پنهان میسازه. برای همین قبل از سراغرفتن به useEffect بپرس اصلاً لازمش داری؟ خیلی از افکتها در واقع state مشتقشده، یه event handler یا data fetchingای هستن که فریمورک بهتر انجامش میده؛ و بهترین رفع یه باگ useEffect معمولاً یکی کمتر کردن useEffectـه.
نکات کلیدی:
- ریشهی تقریباً همهی باگهای useEffect اینه که افکت بیشتر از انتظار اجرا میشه
- نبود آرایهی وابستگی، حلقهی رندر بینهایت میسازه؛ آرایهی خالی یعنی فقط موقع mount
- React وابستگیها رو با مرجع مقایسه میکنه، پس آبجکت/آرایه/تابع تازه تو هر رندر حلقهی پنهان میسازه
- راهحل: پایدارکردن مرجع با useMemo (یا useCallback برای تابع) یا وابستهشدن به مقدار primitive
- قانون react-hooks/exhaustive-deps این باگها رو زودتر میگیره؛ بهترین رفع معمولاً یکی کمتر کردن useEffectـه




