Advance React শিখুন বাংলায় (পর্ব ৬) — React.memo ও Re-render নিয়ন্ত্রণ শিখুন

React.memo ও Re-render নিয়ন্ত্রণ

আগের পর্বগুলোতে আমরা 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:

  1. উপরের memo/non-memo তালিকা ডেমো নিজে বানাও, তালিকার আইটেম সংখ্যা ২০-৩০-এ বাড়িয়ে পার্থক্য দেখো।
  2. React Developer Tools এক্সটেনশন ইনস্টল করো (যদি না থাকে), "Highlight updates" চালু করে নিজের কোনো প্রজেক্টে re-render দেখো।
  3. একটা custom comparison function লেখো যেটা শুধু একটা নির্দিষ্ট prop চেক করে, বাকি সব উপেক্ষা করে।
  4. পর্ব ৪-এর TodoApp নিজে বানাও, তারপর DevTools Profiler দিয়ে যাচাই করো TodoItem-গুলো টাইপ করার সময় সত্যিই re-render হচ্ছে না।

পরের পর্বে যা থাকছে

পরের পর্বে আমরা শিখব Error Boundary ও Code Splitting — একটা component ক্র্যাশ করলে পুরো অ্যাপ যেন না ভেঙে যায়, আর React.lazySuspense দিয়ে কীভাবে অ্যাপের সাইজ ভাগ করে দ্রুত লোড করা যায়।

একটি মন্তব্য পোস্ট করুন

নবীনতর পূর্বতন

যোগাযোগ ফর্ম