Derived Data و حذف محاسبات غیرضروری
در درسهای قبل این فصل، ابزارهای memoization را دیدیم که به یک مشکل واقعی و اندازهگیریشده پاسخ میدهند. اما بهترین بهینهسازی، گاهی اصلاً نیازی به این ابزارها ندارد؛ چون خودِ نیاز به محاسبه تکراری، از طراحی نادرست State میآید. در این درس، به یکی از همان مفاهیم آشنا در فصل سوم، یعنی Derived State، از زاویه Performance برمیگردیم.
یادآوری: Derived Data چیست
در فصل سوم دیدیم که برخی مقادیر را نباید بهعنوان State مستقل نگه داشت، چون میتوان آنها را مستقیماً از روی State یا Props موجود محاسبه کرد. همان اصل، پایه این درس هم هست؛ فقط اینبار از زاویه Performance به آن نگاه میکنیم.
محاسبه مستقیم، معمولاً کافی است
پیش از رفتن سراغ useMemo، اول باید بپرسیم: آیا اصلاً به این مقدار باید بهشکل State جداگانه فکر کرد؟ اگر پاسخ منفی است، سادهترین و کمهزینهترین راه، محاسبه مستقیم آن در بدنه کامپوننت است، بدون هیچ Hookی:
function Cart({ items }) {
const total = items.reduce((sum, item) => sum + item.price, 0);
return <p>مجموع: {total}</p>;
}
برای اکثر آرایههای کوچک و متوسط، این محاسبه در هر Render، کاملاً بیضرر و سریعتر از هزینه اضافه کردن useMemo است؛ دقیقاً همان نکتهای که در درس قبل درباره هزینه مقایسه دیدیم.
چه زمانی محاسبه مستقیم دیگر کافی نیست
وقتی داده بهقدری بزرگ است که محاسبه، زمان محسوسی میبرد (مثلاً پردازش روی دهها هزار آیتم)، و این محاسبه واقعاً با هر Render، حتی برای تغییرات بیربط، تکرار میشود، تازه آنجا useMemo معنا پیدا میکند؛ همان چیزی که در درس چهارم این فصل دیدیم.
یک راهحل جایگزین: کوچکتر کردن وابستگی به Re-render
گاهی بهجای memoize کردن یک محاسبه سنگین، بهتر است اصلاً از رخ دادن Re-render بیربط جلوگیری کنیم. برای مثال، اگر یک محاسبه فقط به بخشی از State وابسته است، میتوان آن بخش را در یک کامپوننت جدا و کوچکتر قرار داد تا فقط همان بخش، با تغییر مربوطه، دوباره Render شود؛ نه کل صفحه.
// قبل: هر تغییر در App، محاسبه سنگین را هم اجرا میکند
function App() {
const [query, setQuery] = useState("");
const [theme, setTheme] = useState("light");
const results = heavySearch(query); // با تغییر theme هم دوباره اجرا میشود
return (
<div>
<SearchResults results={results} />
<ThemeToggle theme={theme} onToggle={setTheme} />
</div>
);
}
// بعد: جدا کردن بخشهای مستقل
function SearchSection() {
const [query, setQuery] = useState("");
const results = heavySearch(query);
return <SearchResults results={results} />;
}
function App() {
const [theme, setTheme] = useState("light");
return (
<div>
<SearchSection />
<ThemeToggle theme={theme} onToggle={setTheme} />
</div>
);
}
در نسخه دوم، تغییر theme دیگر باعث اجرای heavySearch نمیشود، چون آن محاسبه داخل یک کامپوننت کاملاً جدا قرار گرفته که اصلاً به theme وابسته نیست. این راهحل، بدون هیچ memoization ای، مشکل اصلی را حل میکند.
ترتیب درست تصمیمگیری
با کنار هم گذاشتن این فصل، یک ترتیب منطقی به دست میآید:
- آیا این مقدار اصلاً باید State جداگانه باشد، یا Derived است؟ (فصل سوم)
- آیا محاسبه مستقیم، بدون هیچ Hookی، بهاندازه کافی سریع است؟
- آیا میتوان با جدا کردن ساختار کامپوننتها، از Re-render غیرضروری همان محاسبه جلوگیری کرد؟
- فقط اگر هیچکدام کافی نبود و مشکل با اندازهگیری تأیید شد، سراغ useMemo برویم.
مثال قابل اجرا
در این مثال، محاسبه جستوجو داخل یک کامپوننت جدا قرار گرفته تا با تغییر تم، دوباره اجرا نشود؛ بدون استفاده از useMemo.
