আগের পর্বগুলোতে আমরা React.memo কয়েকবার উল্লেখ করেছি, কিন্তু বিস্তারিত দেখিনি। এই পর্বে সেটাই করব — কেন component অকারণে re-render হয়, memo দিয়ে কীভাবে সেটা আটকানো যায়, আর React DevTools দিয়ে কীভাবে সমস্যা খুঁজে বের করা যায়।
Component কখন Re-render হয়?
React এর হাতেখড়ি পর্ব ৫-এ শিখেছিলাম setState কল করলে component re-render হয়। কিন্তু একটা গুরুত্বপূর্ণ ব্যাপার — যখন একটা প্যারেন্ট re-render হয়, তার সব চাইল্ড component-ও ডিফল্টভাবে re-render হয়, তাদের নিজস্ব props বা state পাল্টেছে কিনা তা নির্বিশেষে।
| কারণ | ব্যাখ্যা |
|---|---|
| নিজের state পাল্টালে | useState/useReducer-এর setter কল হলে |
| প্যারেন্ট re-render হলে | চাইল্ডের নিজের কিছু না পাল্টালেও, ডিফল্টভাবে re-render হয় |
| ব্যবহৃত Context পাল্টালে | পর্ব ২-এ শেখা Context ব্যবহারকারী সব component |
ছোট প্রজেক্টে এই "ডিফল্ট" আচরণ কোনো সমস্যা না — React খুব দ্রুত, বেশিরভাগ re-render চোখে পড়েও না। সমস্যা হয় তখনই যখন কোনো চাইল্ড component ভারী (অনেক DOM এলিমেন্ট, জটিল গণনা), আর সেটা বারবার অকারণে re-render হতে থাকে।
React.memo — বেসিক ব্যবহার
React.memo একটা component-কে "মনে রাখতে" শেখায় — যদি props আগেরবারের মতোই থাকে (shallow comparison দিয়ে), React সেই component-এর render skip করে দেয়, আগের রেজাল্ট পুনর্ব্যবহার করে।
// সাধারণ component
function ProductCard({ product }) {
console.log("ProductCard render হলো:", product.name);
return <div>{product.name} — ৳{product.price}</div>;
}
// React.memo দিয়ে wrap করা
const MemoizedProductCard = React.memo(function ProductCard({ product }) {
console.log("ProductCard render হলো:", product.name);
return <div>{product.name} — ৳{product.price}</div>;
});
এখন MemoizedProductCard-এর product prop যদি আগেরবারের সাথে "সমান" থাকে (একই reference বা একই primitive মান), React re-render skip করবে।
Shallow Comparison — কী মানে?
React.memo প্রতিটা prop আলাদাভাবে তুলনা করে, কিন্তু গভীরভাবে না — অবজেক্ট/অ্যারের ভেতরের মান একই হলেও, যদি reference ভিন্ন হয় (Advanced JS পর্ব ৫-এর Spread দিয়ে নতুন অবজেক্ট বানানো মনে করো), তাহলে React এটাকে "পাল্টেছে" হিসেবে দেখবে।
function ParentComponent() {
const [count, setCount] = React.useState(0);
// ❌ প্রতিবার render হলে product একটা নতুন অবজেক্ট, যদিও মান একই
const product = { name: "মাউস", price: 850 };
return (
<div>
<button onClick={() => setCount(count + 1)}>কাউন্ট: {count}</button>
<MemoizedProductCard product={product} />
{/* count পাল্টালেও product একই মান, কিন্তু নতুন reference —
তাই React.memo কাজ করবে না, প্রতিবার re-render হবে */}
</div>
);
}
এই সমস্যার সমাধান — পর্ব ৩-এর useMemo দিয়ে product-এর reference স্থিতিশীল রাখা, অথবা আরও ভালো, product-কে component-এর বাইরে বা একটা স্থির উৎস (props/state) থেকে আনা।
লাইভ ডেমো — memo সহ ও ছাড়া তালিকা
নিচে একটা কাউন্টার বাড়ালে দুইটা লিস্ট দেখানো হবে — একটায় সাধারণ কার্ড (প্রতিবার সব re-render হয়), আরেকটায় memo করা কার্ড (শুধু নিজের ডেটা পাল্টালে re-render হয়)। রঙের ফ্ল্যাশ দেখে বোঝা যাবে কোনটা re-render হচ্ছে।
"কাউন্ট বাড়াও" চাপো — বাঁ পাশের কার্ডগুলো প্রতিবার ফ্ল্যাশ করবে (re-render), ডান পাশেরগুলো করবে না, কারণ name prop পাল্টায়নি।
Custom Comparison Function
ডিফল্ট shallow comparison যথেষ্ট না হলে, React.memo-এর দ্বিতীয় আর্গুমেন্টে নিজের তুলনা-ফাংশন দেওয়া যায়।
const ProductCard = React.memo(
function ProductCard({ product }) {
return <div>{product.name} — ৳{product.price}</div>;
},
(prevProps, nextProps) => {
// true রিটার্ন করলে re-render skip হবে (মানে "এরা সমান")
return prevProps.product.id === nextProps.product.id
&& prevProps.product.price === nextProps.product.price;
// শুধু id আর price তুলনা করছি, বাকি প্রপার্টি (যেমন description) পাল্টালেও re-render হবে না
}
);
সতর্কতা: Custom comparison ফাংশন useMemo/useCallback-এর মতোই — এটাও অতিরিক্ত কম্পিউটেশন, তাই শুধু আসল পারফরম্যান্স সমস্যা থাকলেই ব্যবহার করা উচিত।
memo + useCallback + useMemo — তিনটা একসাথে
পর্ব ৩-৪-এর সাথে মিলিয়ে — একটা optimized component সাধারণত তিনটাই একসাথে ব্যবহার করে।
const ProductCard = React.memo(function ProductCard({ product, onAddToCart }) {
return (
<div>
{product.name}
<button onClick={() => onAddToCart(product.id)}>কার্টে যোগ</button>
</div>
);
});
function ProductList({ products }) {
const [cart, setCart] = React.useState([]);
const [filter, setFilter] = React.useState("");
// useCallback (পর্ব ৪) — ফাংশন রেফারেন্স স্থিতিশীল, memo কাজ করবে
const handleAddToCart = React.useCallback((id) => {
setCart((prev) => [...prev, id]);
}, []);
// useMemo (পর্ব ৩) — ফিল্টার করা তালিকা শুধু দরকার হলে আবার গণনা
const filteredProducts = React.useMemo(() => {
return products.filter((p) => p.name.includes(filter));
}, [products, filter]);
return (
<div>
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
{filteredProducts.map((product) => (
<ProductCard key={product.id} product={product} onAddToCart={handleAddToCart} />
))}
</div>
);
}
এই তিনটা একসাথে কাজ করে — useMemo ফিল্টার করা অ্যারে অপ্রয়োজনে আবার না বানায়, useCallback handleAddToCart-এর reference স্থির রাখে, আর React.memo সেই স্থিতিশীল props দেখে অপ্রয়োজনীয় ProductCard re-render আটকায়।
React DevTools দিয়ে প্রোফাইল করা
Chrome/Firefox-এর React Developer Tools এক্সটেনশন ইনস্টল করলে একটা "Profiler" ট্যাব পাওয়া যায়, যেখানে রেকর্ড করে দেখা যায় কোন component কতবার, কেন re-render হয়েছে।
| ধাপ | কাজ |
|---|---|
| ১ | DevTools-এ "Profiler" ট্যাব খোলা, রেকর্ড বাটনে চাপা |
| ২ | অ্যাপে কিছু ইন্টারঅ্যাকশন করা (বাটন চাপা, টাইপ করা) |
| ৩ | রেকর্ড বন্ধ করে ফলাফল দেখা — কোন component কতবার render হয়েছে, কতটা সময় লেগেছে |
| ৪ | "Settings" থেকে "Highlight updates" চালু করলে re-render হওয়া component স্ক্রিনে রঙিন বর্ডার দিয়ে ফ্ল্যাশ করে দেখায় (উপরের ডেমোতে যেটা ম্যানুয়ালি বানানো হয়েছে) |
এই টুল দিয়ে সিদ্ধান্ত নেওয়া যায় — কোন component-এ সত্যিই memo দরকার, আর কোনটাতে অপ্রয়োজনীয়।
কখন React.memo ব্যবহার করবে না
- ছোট, সহজ component (একটা
<span>বা একটা আইকন) — re-render এমনিতেই এত দ্রুত যে memo-এর তুলনা খরচই বেশি - Props প্রায় সবসময়ই পাল্টায় (যেমন প্রতিবার নতুন timestamp) — তখন memo কখনোই match পাবে না, শুধু বাড়তি কাজ
- Premature optimization — DevTools দিয়ে আসল সমস্যা নিশ্চিত না হয়ে সবজায়গায় memo বসিয়ে দেওয়া
নিজে চেষ্টা করো
Exercise:
- উপরের memo/non-memo তালিকা ডেমো নিজে বানাও, তালিকার আইটেম সংখ্যা ২০-৩০-এ বাড়িয়ে পার্থক্য দেখো।
- React Developer Tools এক্সটেনশন ইনস্টল করো (যদি না থাকে), "Highlight updates" চালু করে নিজের কোনো প্রজেক্টে re-render দেখো।
- একটা custom comparison function লেখো যেটা শুধু একটা নির্দিষ্ট prop চেক করে, বাকি সব উপেক্ষা করে।
- পর্ব ৪-এর
TodoAppনিজে বানাও, তারপর DevTools Profiler দিয়ে যাচাই করোTodoItem-গুলো টাইপ করার সময় সত্যিই re-render হচ্ছে না।
পরের পর্বে যা থাকছে
পরের পর্বে আমরা শিখব Error Boundary ও Code Splitting — একটা component ক্র্যাশ করলে পুরো অ্যাপ যেন না ভেঙে যায়, আর React.lazy ও Suspense দিয়ে কীভাবে অ্যাপের সাইজ ভাগ করে দ্রুত লোড করা যায়।