ما هي useMemo؟
هو هوك يرجّع لك قيمة مُحاسَبة (computed value) مُخزّنة تُعاد حسابها فقط عندما تتغيّر الاعتمادات (dependencies). فكر فيها كـ “كاش” لنتيجة دالة غالية التكلفة.
التوقيع:
const memoizedValue = useMemo(() => computeSomething(a, b), [a, b]);JSX- الدالة داخل
useMemoتُنفَّذ فقط إذا تغيّر أحد عناصر المصفوفة[a, b]. - مفيدة لتجنّب الحسابات الثقيلة كل رندر، أو للمحافظة على المساواة المرجعية (Referential Equality) لقيم/كائنات نمررها كمزايا (props) أو نعتمد عليها في
useEffect.
متى أستخدمها؟
- عند وجود حساب ثقيل (فرز/تجميع/فلترة ضخمة، صياغة بيانات رسومات…).
- عندما تحتاج ثبات المرجع لكائن/مصفوفة مُمَرّرة لـ child memoized لتقليل rerenders.
- لا تستخدمها لكل شيء؛ لو الحساب رخيص، اتركها بسيطة بلا
useMemo.
أمثلة أساسية
1) حساب مكلف (Expensive Computation)
import { useMemo, useState } from "react";
function heavySum(n) {
// محاكاة عملية ثقيلة
let s = 0;
for (let i = 0; i < 5_000_000; i++) s += (i % 10);
return s + n;
}
export default function ExpensiveExample() {
const [n, setN] = useState(0);
const [text, setText] = useState("");
const result = useMemo(() => heavySum(n), [n]);
// ستحسب فقط عند تغيّر n، الكتابة في input الثاني لن تعيد الحساب
return (
<div>
<input type="number" value={n} onChange={(e) => setN(+e.target.value)} />
<input value={text} onChange={(e) => setText(e.target.value)} placeholder="لا يؤثر على الحساب" />
<p>النتيجة: {result}</p>
</div>
);
}
JSX2) فلترة وفرز قائمة كبيرة
import { useMemo, useState } from "react";
export default function FilterSortList({ items }) {
const [q, setQ] = useState("");
const [sortAsc, setSortAsc] = useState(true);
const visible = useMemo(() => {
const filtered = items.filter(it =>
it.name.toLowerCase().includes(q.toLowerCase())
);
return filtered.sort((a, b) =>
sortAsc ? a.price - b.price : b.price - a.price
);
}, [items, q, sortAsc]);
return (
<div>
<input value={q} onChange={(e) => setQ(e.target.value)} placeholder="ابحث..." />
<button onClick={() => setSortAsc(s => !s)}>تبديل الترتيب</button>
<ul>{visible.map(it => <li key={it.id}>{it.name} - ${it.price}</li>)}</ul>
</div>
);
}
JSX3) تثبيت مرجع كائن/مصفوفة تمرّرها لطفل (لمنع rerenders)
import { useMemo, useState, memo } from "react";
const Child = memo(function Child({ options }) {
// سيُعاد رندر Child فقط عندما يتغيّر محتوى options فعلاً
console.log("Child render");
return <pre>{JSON.stringify(options)}</pre>;
});
export default function Parent() {
const [count, setCount] = useState(0);
// بدون useMemo: كل رندر ينشئ كائنًا جديدًا ⇒ Child يعيد الرندر
const options = useMemo(
() => ({ page: 1, pageSize: 20, show: count % 2 === 0 }),
[count] // يتغير عند تغيّر count (فعلاً المحتوى يتغير)
);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+1</button>
<Child options={options} />
</div>
);
}
JSX4) اشتقاق حالة من مدخلات متعددة (Derived Data)
import { useMemo, useState } from "react";
export default function CartTotals({ items }) {
const [taxRate, setTaxRate] = useState(0.15);
const { subtotal, tax, total } = useMemo(() => {
const subtotal = items.reduce((s, it) => s + it.price * it.qty, 0);
const tax = subtotal * taxRate;
return { subtotal, tax, total: subtotal + tax };
}, [items, taxRate]);
return (
<div>
<p>Subtotal: {subtotal.toFixed(2)}</p>
<p>Tax: {tax.toFixed(2)}</p>
<p>Total: {total.toFixed(2)}</p>
<button onClick={() => setTaxRate(r => (r === 0.15 ? 0.2 : 0.15))}>
Toggle Tax
</button>
</div>
);
}
JSXuseMemo vs useCallback vs memo
useMemo(fn, deps): يخزّن قيمة ناتجة عن دالة.useCallback(fn, deps): يخزّن دالة نفسها (مرجع ثابت)؛ مكافئ لـuseMemo(() => fn, deps).memo(Component): يغلف كومبوننت ليمنع رندرة إلا إذا تغيّرت props بقيمة مختلفة (shallow compare).
غالبًا نستخدمuseMemo/useCallbackمعmemoلتثبيت مراجع props.
ملاحظات وأفضل ممارسات
- لا تبالغ:
useMemoله كلفة (مقارنة deps وتخزين). استخدمه عندما:- الحساب فعلاً مكلف، أو
- تحتاج ثبات مرجع لكائن/مصفوفة/دالة لخفض rerenders.
- اجعل قائمة الاعتمادات دقيقة؛ لو نسيت اعتماد، قد تعرض قيمة قديمة.
- حافظ على الدالة داخل
useMemoخالية من الآثار الجانبية (Pure). لا تعمل setState داخلها. - لو كنت تحتاج قراءة/جلب بيانات → هذا دور
useEffectوليسuseMemo.
أخطاء شائعة
- ❌ وضع دالة fetch داخل
useMemoبدلuseEffect. - ❌ استخدام
useMemoحول أشياء رخيصة (لن تربح شيئًا). - ❌ نسيان deps أو تمرير كائنات غير ثابتة في deps (فتُحسب كل مرة).
لمحة TypeScript (اختياري)
const list = useMemo<string[]>(() => data.map(d => d.name), [data]);JSXمثال صغير يجمع useMemo مع memo وuseCallback
import { memo, useCallback, useMemo, useState } from "react";
const List = memo(function List({ items, onPick }) {
console.log("List render");
return (
<ul>
{items.map(it => (
<li key={it.id} onClick={() => onPick(it.id)}>
{it.label}
</li>
))}
</ul>
);
});
export default function Combo() {
const [q, setQ] = useState("");
const [picked, setPicked] = useState(null);
const raw = useMemo(
() => Array.from({ length: 1000 }, (_, i) => ({ id: i, label: `Item ${i}` })),
[] // يُنشأ مرة واحدة
);
const filtered = useMemo(() => {
if (!q) return raw;
return raw.filter(x => x.label.toLowerCase().includes(q.toLowerCase()));
}, [raw, q]); // فلترة مكلفة تُحسب فقط عند الحاجة
const handlePick = useCallback((id) => setPicked(id), []); // مرجع دالة ثابت
return (
<div>
<input value={q} onChange={(e) => setQ(e.target.value)} placeholder="فلترة..." />
<List items={filtered} onPick={handlePick} />
<p>المختار: {picked ?? "لا شيء"}</p>
</div>
);
}
JSX