useMemo
useMemo
হল একটি React হুক, যা আপনাকে রি-রেন্ডারিং এর সময় কোনো রেজাল্ট মেমোরিতে সংরক্ষণ করে রাখতে দেয়।
const cachedValue = useMemo(calculateValue, dependencies)
রেফারেন্স
useMemo(calculateValue, dependencies)
useMemo
কে আপনার কম্পোনেন্ট এর একেবারে উপরের স্তরে কল করতে হবে যাতে রি-রেন্ডারগুলির মধ্যে রেজাল্ট cache সংরক্ষণ করা যায।
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
প্যারামিটারস
-
calculateValue
: এটি এমন একটি ফাংশন যা আপনি cache করতে চান তার ভ্যালু ক্যালকুলেট করে। এটি পিওর হওয়া উচিত, কোনো আর্গুমেন্ট নেওয়া উচিত না, এবং যেকোনো টাইপের একটি ভ্যালু রিটার্ন করা উচিত। React প্রাথমিক রেন্ডারের সময় আপনার ফাংশন কল করবে। পরবর্তী রেন্ডারগুলিতে, যদি dependencies শেষ রেন্ডার থেকে পরিবর্তিত না হয়, তাহলে একই মান আবার রিটার্ন করবে। অন্যথায়, এটিcalculateValue
, কল করবে, এর ফলাফল রিটার্ন করবে, এবং তা সংরক্ষণ করবে যাতে পরবর্তীতে পুনরায় ব্যবহার করা যায়। -
dependencies
: এটি হলcalculateValue
কোডের ভিতরে উল্লেখিত সকল রিঅ্যাক্টিভ মানের একটি রেফারেন্স। রিঅ্যাক্টিভ মান অন্তর্ভুক্ত করে props, state, এবং আপনার কম্পোনেন্ট বডির ভিতরে ভেরিয়েবল এবং ফাংশন সরাসরি ডিক্লেয়ার করে। যদি আপনার লিন্টার React এর জন্য কনফিগার করা থাকে, তাহলে এটি প্রতিটি রিঅ্যাক্টিভ মান সঠিকভাবে ডিপেন্ডেন্সিস নির্দিষ্ট করা আছে কিনা তা যাচাই করবে। ডিপেন্ডেন্সিগুলির তালিকায় একটি স্থির সংখ্যক আইটেম থাকতে হবে এবং এটি ইনলাইনে লেখা হতে হবে যেমন [dep1, dep2, dep3]। React প্রতিটি ডিপেন্ডেন্সি তার আগের মানের সাথেObject.is
comparison ব্যবহার করে তুলনা করবে।
রিটার্নস
প্রাথমিক রেন্ডারে, useMemo
কোনো আর্গুমেন্ট ছাড়াই calculateValue
কল করে এবং ফলাফল রিটার্ন করে।
পরবর্তী রেন্ডারগুলিতে, এটি হয় শেষ রেন্ডার থেকে ইতিমধ্যে সংরক্ষিত একটি মান রিটার্ন করবে (যদি ডিপেন্ডেন্সিসগুলি পরিবর্তিত না হয়ে থাকে), অথবা আবার calculateValue
কল করবে, এবং calculateValue
যে ফলাফল দিয়েছে সেটি রিটার্ন করে দেবে।
সতর্কতা
useMemo
একটি হুক, তাই আপনি এটি শুধুমাত্র আপনার কম্পোনেন্টের শীর্ষ স্তরে অথবা আপনার নিজের হুকগুলিতে কল করতে পারেন। আপনি এটি লুপ অথবা কন্ডিশনের ভিতরে কল করতে পারবেন না। যদি আপনার এমন প্রয়োজন হয়, তাহলে একটি নতুন কম্পোনেন্ট তৈরি করে স্টেটটি তাতে সরিয়ে নিন।- Strict Mode এ, React আপনার calculation ফাংশনটি দুইবার কল করবে যাতে আপনার আকস্মিক ভুল খুঁজে পেতে সাহায্য হয়। এটি শুধুমাত্র ডেভেলপমেন্ট-এর জন্য এবং প্রোডাকশনে কোনো প্রভাব ফেলবে না। যদি আপনার calculation ফাংশনটি পিওর হয় (যেমনটি হওয়া উচিত), তাহলে এটি আপনার লজিকে কোনো প্রভাব ফেলা উচিত নয়। দুইবার কল করলেও একবার ফলাফল প্রেরণ করবে।
- React ক্যাশে সংরক্ষিত মানটি ত্যাগ করবে না, যদি না তা করার বিশেষ কোনো কারণ থাকে। উদাহরণস্বরূপ, ডেভেলপমেন্টে, যখন আপনি আপনার কম্পোনেন্টের ফাইল এডিট করেন, React ক্যাশে তখন তা ত্যাগ করে। ডেভেলপমেন্ট এবং প্রোডাকশন উভয়ক্ষেত্রেই, React ক্যাশে ত্যাগ করবে যদি আপনার কম্পোনেন্ট প্রাথমিক মাউন্টের সময় সাসপেন্ড হয়। ভবিষ্যতে, React আরও ফিচার যোগ করতে পারে যা ক্যাশে ত্যাগ করার সুবিধা নেয়—উদাহরণস্বরূপ, যদি React ভবিষ্যতে ভার্চুয়ালাইজড লিস্টের জন্য অন্তর্নির্মিত সমর্থন যোগ করে, তবে ভার্চুয়ালাইজড টেবিল ভিউপোর্ট থেকে স্ক্রল করে বাইরে যাওয়া আইটেমগুলির জন্য ক্যাশে ত্যাগ করা যুক্তিসঙ্গত হবে। এটি ঠিক থাকা উচিত যে আপনি যদি useMemo কে শুধুমাত্র একটি পারফরমেন্স অপটিমাইজেশন হিসেবে ব্যবহার করেন। নতুবা, একটি স্টেট ভেরিয়েবল অথবা একটি ref আরও উপযুক্ত হতে পারে।
ব্যবহারবিধি
খরুচে recalculation এড়ানো
রি-রেন্ডারের মধ্যে একটি calculation cache সংরক্ষণ করতে, useMemo
আপনার কম্পোনেন্টের শীর্ষ স্তরে কল করে রাখুন:
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
useMemo
তে আপনাকে দুটি জিনিস পাস করতে হবে:
- calculation ফাংশন যা কোনো আর্গুমেন্ট নেয় না, যেমন () =>, এবং আপনি যা calculation করতে চেয়েছিলেন তা রিটার্ন করে।
- ডিপেন্ডেন্সিস তালিকা যা আপনার calculation এর ভিতরে ব্যবহৃত আপনার কম্পোনেন্টের প্রতিটি মান অন্তর্ভুক্ত করে।
প্রাথমিক রেন্ডারে, useMemo
থেকে আপনি যে মানটি পাবেন তা আপনার calculation করার ফলাফল হবে।
প্রতিটি পরবর্তী রেন্ডারে, React ডিপেন্ডেন্সিস আগের রেন্ডারে আপনার পাস করা ডিপেন্ডেন্সিসগুলির সাথে তুলনা করবে। যদি ডিপেন্ডেন্সিসগুলির কোনোটিই পরিবর্তিত না হয় (যেমন Object.is
দিয়ে তুলনা করা), useMemo
আগে আপনি যে মানটি calculation করেছিলেন তা রিটার্ন করে দেবে। অন্যথায়, React আপনার calculation টি পুনরায় চালাবে এবং নতুন মানটি রিটার্ন করবে।
অন্য কথায়, useMemo
তার ডিপেন্ডেন্সিসগুলির পরিবর্তন না হওয়া পর্যন্ত রি-রেন্ডারের সময় এটি আগের calculation এর ফলাফল ক্যাশে সংরক্ষণ করে। In other words,
একটি উদাহরণের মাধ্যমে আমরা দেখব এটির উপকারীতা
ডিফল্ট হিসেবে, React প্রতিবার আপনার কম্পোনেন্টের সম্পূর্ণ বডি পুনরায় রান করবে যখন তা রি-রেন্ডার হবে। উদাহরণস্বরূপ, যদি এই TodoList
এর স্টেট আপডেট হয় অথবা এটি তার প্যারেন্ট থেকে নতুন props পায়, তাহলে filterTodos
ফাংশনটি পুনরায় রান করবে:
function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}
সাধারণত, এটি একটি সমস্যা নয় কারণ বেশিরভাগ calculation খুবই দ্রুত হয়। তবে, যদি আপনি একটি বড় array ফিল্টার করছেন অথবা পরিবর্তন করছেন, অথবা কিছু এক্সপেন্সিভ calculation করেছেন, এবং আপনি চাইতেছেন ডেটা পরিবর্তিত না হলে এটি আবার রেন্ডার না হক। যদি todos
এবং tab
উভয়ই আগের রেন্ডারের যা ছিল তাই থাকে, তাহলে useMemo
ব্যবহার করে আগের মতো calculation পেতে পারেন এবং আপনি visibleTodo
পুনরায় ব্যবহার করে আগের calculation পেতে পারেন।
এই ধরণের ক্যাশিংকে মেমোইজেশন বলা হয়।
গভীরভাবে জানুন
সাধারণভাবে, আপনি যদি হাজার হাজার অবজেক্ট তৈরি করেন বা তাদের উপর লুপ চালান, তাহলে সম্ভবত এটি এক্সপেন্সিভ নয়। আপনি যদি আরও নিশ্চিত হতে চান, তাহলে আপনি একটি কোডের অংশে সময় পরিমাপ করতে console log যোগ করতে পারেন:
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
আপনি যে ইন্টার্যাকশনটি পরিমাপ করছেন তা সম্পাদন করুন (উদাহরণ স্বরূপ, ইনপুটে টাইপ করা)। তারপর আপনি আপনার কনসোলে filter array: 0.15ms
এর মতো লগ দেখতে পাবেন। যদি মোট লগ করা সময় উল্লেখযোগ্য পরিমাণে জমা হয় (ধরুন, 1ms
বা তার বেশি), তাহলে সেই calculation টি মেমোইজ করা যুক্তিসঙ্গত হতে পারে। একটি পরীক্ষা হিসেবে, আপনি তখন useMemo
ব্যবহার করে সেই calculation টি পুনরায় যাচাই করতে পারেন যে সেই ইন্টার্যাকশনের জন্য মোট লগ করা সময় কমেছে কিনা:
console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Skipped if todos and tab haven't changed
}, [todos, tab]);
console.timeEnd('filter array');
useMemo
প্রথম রেন্ডারটি দ্রুত করে না। এটি কেবল আপডেটে অপ্রয়োজনীয় কাজ এড়াতে আপনাকে সাহায্য করে।
মনে রাখবেন যে আপনার মেশিন সম্ভবত আপনার ব্যবহারকারীদের মেশিনের চেয়ে দ্রুততর, তাই কৃত্রিম ধীর গতির মাধ্যমে পারফরমেন্স পরীক্ষা করা একটি ভাল প্র্যাক্টিস। উদাহরণস্বরূপ, CPU Throttling অপশন এই উদ্দেশ্যে অফার করে।
আরও মনে রাখবেন যে ডেভেলপমেন্টে পারফরমেন্স আপনাকে সবচেয়ে সঠিক ফলাফল দেবে না। (উদাহরণ স্বরূপ, যখন Strict Mode চালু থাকে, আপনি প্রতিটি কম্পোনেন্ট একবারের বদলে দুবার রেন্ডার হতে দেখবেন।) সবচেয়ে সঠিক সময় পাওয়ার জন্য, আপনার অ্যাপটি প্রোডাকশনের জন্য বিল্ড করুন এবং এটি আপনার ব্যবহারকারীদের মতো একটি ডিভাইসে পরীক্ষা করুন।
গভীরভাবে জানুন
আপনার অ্যাপ যদি এই সাইটের মতো হয়, এবং বেশিরভাগ ইন্টার্যাকশন স্থূল (যেমন একটি পৃষ্ঠা বা একটি পুরো সেকশন রিপ্লেস করা) হয়, তাহলে সাধারণত মেমোইজেশন অপ্রয়োজনীয়। অন্যদিকে, যদি আপনার অ্যাপ একটি ড্রয়িং এডিটরের মতো হয়, এবং বেশিরভাগ ইন্টার্যাকশন সূক্ষ্ম (যেমন আকৃতি সরানো) হয়, তাহলে আপনি মেমোইজেশনকে খুবই উপকারী মনে করতে পারেন।
useMemo
এর মাধ্যমে অপ্টিমাইজেশন কেবল কয়েকটি ক্ষেত্রে মূল্যবান:
useMemo
এ যে calculation টি আপনি করছেন তা লক্ষণীয়ভাবে ধীর, এবং এর ডিপেন্ডেন্সিসগুলি খুব কম পরিবর্তিত হয়।- আপনি এটি একটি কম্পোনেন্টকে প্রপ হিসেবে পাস করেন যা
memo
.. দ্বারা মোড়ানো। মান যদি অপরিবর্তিত থাকে তাহলে আপনি কি-রেন্ডারিং এড়াতে চান। মেমোইজেশন আপনার কম্পোনেন্টকে শুধুমাত্র তখন রি-রেন্ডার করতে দেয় যখন ডিপেন্ডেন্সিসগুলি একই নয়। - আপনি যে মানটি পাস করছেন তা পরবর্তীতে কোনো হুকের নির্ভরতা হিসেবে ব্যবহৃত হয়। উদাহরণস্বরূপ, হয়তো অন্য একটি
useMemo
calculation এর মান এটির উপর নির্ভর করে। অথবা হয়তো আপনি এই মানটির উপরuseEffect.
. থেকে নির্ভর করছেন।
অন্য ক্ষেত্রে, useMemo
ব্যবহার করে কোনো calculation মোড়ানোর কোনো সুবিধা নেই। তবে করার ফলে কোনো গুরুতর ক্ষতি হয় না, তাই কিছু টিম প্রতিটি ক্ষেত্রে আলাদাভাবে চিন্তা না করে যতটা সম্ভব মেমোইজ করে। এই পদ্ধতির অসুবিধা হল কোড কম পাঠযোগ্য হয়। এছাড়া, সব মেমোইজেশন কার্যকর নয়: “সর্বদা নতুন” একটি একক মান পুরো কম্পোনেন্টের জন্য মেমোইজেশন ব্যর্থ করতে পারে।
বাস্তবে, কিছু নীতি অনুসরণ করে আপনি অনেক মেমোইজেশন অপ্রয়োজনীয় করে তুলতে পারেন:
- যখন একটি কম্পোনেন্ট অন্যান্য কম্পোনেন্টকে দৃশ্যমানভাবে মোড়ানো, তখন এটিকে JSX চাইল্ড হিসেবে গ্রহণ করতে দিন। এইভাবে, যখন র্যাপার কম্পোনেন্ট নিজের স্টেট আপডেট করে, React জানে যে তার চাইল্ডগুলিকে পুনরায় রেন্ডার করা দরকার নেই।
- লোকাল স্টেটকে প্রাধান্য দিন এবং প্রয়োজনের চেয়ে বেশি স্টেট উপরে পাঠানোর দরকার নাই। উদাহরণ স্বরূপ, ফর্ম এবং কোনো আইটেম হোভার করা আছে কিনা এমন অস্থায়ী স্টেটকে আপনার ট্রির শীর্ষে বা কোনো গ্লোবাল স্টেট লাইব্রেরিতে রাখবেন না।
- আপনার রেন্ডারিং লজিককে পিওর রাখুন। যদি কোনো কম্পোনেন্ট পুনরায় রেন্ডার করার সময় কোনো সমস্যা হয় বা কোনো লক্ষণীয় দৃশ্যমান আর্টিফ্যাক্ট তৈরি হয়, তা আপনার কম্পোনেন্টে একটি বাগ! মেমোইজেশন যোগ করার পরিবর্তে বাগটি ঠিক করুন।
- অপ্রয়োজনীয় এফেক্ট এড়িয়ে চলুন যা স্টেট আপডেট করে। React অ্যাপসে বেশিরভাগ পারফরমেন্স সমস্যা এমন এফেক্ট থেকে উদ্ভূত আপডেটের চেইনের কারণে হয়, যা আপনার কম্পোনেন্টগুলিকে বারবার রেন্ডার করতে বাধ্য করে।
- আপনার এফেক্ট থেকে অপ্রয়োজনীয় নির্ভরতাগুলি সরান। উদাহরণস্বরূপ, মেমোইজেশনের পরিবর্তে এটি প্রায়ই সহজতর হয় কিছু অবজেক্ট বা ফাংশনকে একটি এফেক্টের মধ্যে বা কম্পোনেন্টের বাইরে সরানো।
যদি কোনো নির্দিষ্ট ইন্টার্যাকশন ধীর মনে হয়, React Developer Tools প্রোফাইলার ব্যবহার করে দেখুন কোন কম্পোনেন্টগুলি মেমোইজেশন থেকে সর্বাধিক উপকৃত হবে, এবং প্রয়োজন অনুসারে মেমোইজেশন যোগ করুন। এই নীতিগুলি আপনার কম্পোনেন্টগুলিকে ডিবাগ এবং বোঝা সহজ করে তোলে, তাই যেকোনো ক্ষেত্রে এগুলি অনুসরণ করা ভালো। দীর্ঘ মেয়াদে, আমরা সূক্ষ্ম মেমোইজেশন স্বয়ংক্রিয়ভাবে করা নিয়ে গবেষণা করছি যাতে একবারেই সবার জন্য এই সমস্যা সমাধান করা যায়।
উদাহরণ 1 / 2: useMemo
এর মাধ্যমে পুনর্গণনা স্কিপিং করা
এই উদাহরণে, filterTodos
বাস্তবায়নটি কৃত্রিমভাবে স্লো করা হয়েছে যাতে আপনি দেখতে পারেন যে রেন্ডারিং করার সময় আপনি যে সকল জাভাস্ক্রিপ্ট ফাংশন কল করেন তা যদি সত্যিকারের স্লো হয় তবে কী হয়। ট্যাব পরিবর্তন করে এবং থিম টগল করে দেখুন।
ট্যাব পরিবর্তন করার সময় স্লো মনে হবে কারণ filterTodos
কে ইচ্ছা করেই স্লো করা হয়েছে যখন পুনরায় এক্সেকিউট করে। এটি প্রত্যাশিত কারণ tab পরিবর্তিত হয়েছে, এবং প্রয়োজন হবে পুরো calculation টি পুনরায় চালানো। (যদি আপনি কৌতুহলী হন যে এটি দুইবার কেন চলে, তার ব্যাখ্যা এখানে দেওয়া হয়েছে।)
থিম টগল করুন। useMemo
এর ধন্যবাদ, কৃত্রিম ধীরতার পরেও এটি দ্রুত! ধীর filterTodos
কলটি এড়ানো হয়েছে কারণ todos এবং tab (যা আপনি useMemo
-এ ডিপেন্ডেন্সিস হিসেবে পাস করেছেন) আগের রেন্ডারের পর থেকে পরিবর্তিত হয়নি।
import { useMemo } from 'react'; import { filterTodos } from './utils.js' export default function TodoList({ todos, theme, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); return ( <div className={theme}> <p><b>Note: <code>filterTodos</code> is artificially slowed down!</b></p> <ul> {visibleTodos.map(todo => ( <li key={todo.id}> {todo.completed ? <s>{todo.text}</s> : todo.text } </li> ))} </ul> </div> ); }
কম্পোনেন্টের রি-রেন্ডারিং এড়িয়ে যাওয়া
কিছু ক্ষেত্রে, useMemo
আপনাকে চাইল্ড কম্পোনেন্টের রি-রেন্ডারিং এর পারফরমেন্স অপটিমাইজ করতে সাহায্য করতে পারে। এটি বোঝাতে, ধরা যাক এই TodoList
কম্পোনেন্টটি visibleTodos
কে একটি প্রপ হিসেবে চাইল্ড List
কম্পোনেন্টকে পাস করে:
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
আপনি লক্ষ্য করেছেন যে theme
প্রপ টগল করার সময় অ্যাপটি এক মুহূর্তের জন্য থেমে যায়, কিন্তু যদি আপনি JSX থেকে <List />
সরিয়ে ফেলেন, তাহলে এটি দ্রুত মনে হয়। এটি জানান দেয় যে List
কম্পোনেন্টটি অপটিমাইজ করার চেষ্টা করা যেতে পারে।
ডিফল্ট হিসেবে, যখন একটি কম্পোনেন্ট রি-রেন্ডার হয়, React তার সমস্ত চাইল্ড কম্পোনেন্টকে পুনরায় রেন্ডার করে। এই কারণে, যখন TodoList
একটি ভিন্ন theme রি-রেন্ডার করে, List
কম্পোনেন্টও রি-রেন্ডার হয়। রি-রেন্ডার হবার সময় বেশি calculation প্রয়োজন না হলে এটি সমস্যা নয়। কিন্তু যদি আপনি নিশ্চিত হন যে রি-রেন্ডার ধীর হচ্ছে, তাহলে আপনি List
কে বলতে পারেন যে তার props আগের রেন্ডারের মতোই থাকলে রি-রেন্ডারিং এড়াতে memo
: ব্যবহার করেতে পারেন।
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
এই পরিবর্তনের সাথে, List
তার সমস্ত props আগের রেন্ডারের মতো একই থাকলে রি-রেন্ডারিং এড়িয়ে যাবে। এখানেই calculation ক্যাশে করার গুরুত্ব প্রকাশ পায়! কল্পনা করুন আপনি useMemo
ছাড়া visibleTodos
calculation করেছেন:
export default function TodoList({ todos, tab, theme }) {
// Every time the theme changes, this will be a different array...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... so List's props will never be the same, and it will re-render every time */}
<List items={visibleTodos} />
</div>
);
}
উপরের উদাহরণে, filterTodos
ফাংশন সবসময় একটি আলাদা array তৈরি করে, যেমনটি {}
অবজেক্ট আক্ষরিকভাবে সবসময় একটি নতুন অবজেক্ট তৈরি করে। সাধারণত, এটি কোন সমস্যা নয়, কিন্তু এর মানে হল যে List
props কখনোই একই থাকবে না, এবং আপনার memo
অপটিমাইজেশন কাজ করবে না। এখানেই useMemo
উপযোগী হয়ে ওঠে:
export default function TodoList({ todos, tab, theme }) {
// Tell React to cache your calculation between re-renders...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...so as long as these dependencies don't change...
);
return (
<div className={theme}>
{/* ...List will receive the same props and can skip re-rendering */}
<List items={visibleTodos} />
</div>
);
}
visibleTodos
calculation কে useMemo
দিয়ে মোড়ানোর মাধ্যমে, আপনি নিশ্চিত করেন যে এটি রি-রেন্ডারের মধ্যে একই মান বজায় রাখে (যতক্ষণ না ডিপেন্ডেন্সিস পরিবর্তিত হয়)। কোনো বিশেষ কারণ ছাড়া আপনার calculation কে useMemo
এ মোড়ানো আবশ্যক নয়। এই উদাহরণে, কারণটি হল আপনি এটিকে memo
,](/reference/react/memo), দ্বারা মোড়ানো কম্পোনেন্টকে পাস করেন এবং এটি তাকে রি-রেন্ডারিং এড়াতে দেয়। এখানে useMemo
ব্যবহার করার আরও কিছু কারণ বর্ণিত আছে।
গভীরভাবে জানুন
memo
এ List
কে মোড়ানোর পরিবর্তে, আপনি <List />
JSX নোডটিকেই useMemo
দিয়ে মোড়াতে পারেন:
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}
আচরণটি একই থাকবে। যদি visibleTodos
পরিবর্তিত না হয়, তাহলে List
পুনরায় রেন্ডার করবে না।
<List items={visibleTodos} />
এর মতো JSX নোড হল একটি অবজেক্ট যেমন { type: List, props: { items: visibleTodos } }
। এই অবজেক্ট তৈরি করা খুবই সহজ, কিন্তু React জানে না এর বিষয়বস্তু আগেরবারের মতো একই আছে কিনা। এই কারণে ডিফল্ট হিসেবে, React List
কম্পোনেন্টটি পুনরায় রেন্ডার করে।
তবে, যদি React পূর্ববর্তী রেন্ডারের সময়ে একই JSX দেখে, তাহলে আপনার কম্পোনেন্টটি পুনরায় রেন্ডার করার চেষ্টা করবে না। এর কারণে JSX নোডগুলি অপরিবর্তনীয় আছে বা JSX নোড অবজেক্ট সময়ের সাথে পরিবর্তিত হয়নি, তাই React জানে এটি রি-রেন্ডার এড়িয়ে যাওয়া নিরাপদ। তবে, এর জন্য নোডটি আসলে একই অবজেক্ট হতে হবে, শুধুমাত্র কোডে একই দেখানোর জন্য নয়। এই উদাহরণে useMemo
এটাই করে।
ম্যানুয়ালি JSX নোডগুলিকে useMemo
-এ মোড়ানো সুবিধাজনক নয়। উদাহরণস্বরূপ, আপনি এটি শর্তাধীনভাবে করতে পারবেন না। সাধারণত এই কারণে আপনি JSX নোডগুলিকে মোড়ানোর পরিবর্তে কম্পোনেন্টগুলিকে memo
দিয়ে মোড়ান। এতে করে, props একই থাকলে কম্পোনেন্টের অপ্রয়োজনীয় রি-রেন্ডার এড়ানো সহজ হয়, যা পারফরমেন্সের উন্নতি ঘটায়।
উদাহরণ 1 / 2: useMemo
এবং memo
ব্যবহার করে রি-রেন্ডারিং স্কিপ করা
এই উদাহরণে, List কম্পোনেন্টটি কৃত্রিমভাবে ধীর করা হয়েছে যাতে আপনি দেখতে পারেন একটি স্লো React কম্পোনেন্ট রেন্ডার করার সময় কেমন হয়। ট্যাবগুলি পরিবর্তন করে এবং থিম টগল করে দেখেন।
ট্যাবগুলি পরিবর্তন করার সময় ধীর মনে হয় কারণ এটি স্লো কম্পোনেন্ট List
কে পুনরায় রেন্ডার করতে বাধ্য করে। এটি প্রত্যাশিত কারণ tab
পরিবর্তিত হয়েছে, এবং তাই আপনাকে ব্যবহারকারীর পছন্দ অনুযায়ী স্ক্রিনে দেখাতে হয়।
এরপর, থিম টগল করে দেখুন। useMemo
এবং memo
এর সাহায্যে, এটি কৃত্রিম ধীরতার সত্ত্বেও দ্রুত! List
পুনরায় রেন্ডারিং এড়িয়ে গেছে কারণ visibleTodos
array আগের রেন্ডারের পর থেকে পরিবর্তিত হয়নি। visibleTodos
array পরিবর্তিত হয়নি কারণ todos
এবং tab
(যা আপনি useMemo
-এ ডিপেন্ডেন্সিস হিসেবে পাস করেন) আগের রেন্ডারের পর থেকে পরিবর্তিত হয়নি।
import { useMemo } from 'react'; import List from './List.js'; import { filterTodos } from './utils.js' export default function TodoList({ todos, theme, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); return ( <div className={theme}> <p><b>Note: <code>List</code> is artificially slowed down!</b></p> <List items={visibleTodos} /> </div> ); }
অন্য হুকের ডিপেন্ডেন্সিস মেমোইজ করা
ধরুন আপনি এমন একটি calculation করছেন যা কম্পোনেন্ট বডির মধ্যে সরাসরি তৈরি করা একটি অবজেক্টের উপর নির্ভর করে:
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Caution: Dependency on an object created in the component body
// ...
এই ধরনের একটি অবজেক্টের উপর নির্ভর করা মেমোইজেশনের উদ্দেশ্যকে বানচাল করে। যখন একটি কম্পোনেন্ট পুনরায় রেন্ডার হয়, তখন কম্পোনেন্ট বডির মধ্যে সরাসরি থাকা সমস্ত কোড আবার রান হয়। searchOptions
অবজেক্ট তৈরির কোডের লাইনগুলিও প্রতিবার রি-রেন্ডার হয়। যেহেতু searchOptions
আপনার useMemo কলের উপর নির্ভরশিল, এবং প্রতিবার এটি আলাদা, React জানে যে ডিপেন্ডেন্সিস আলাদা তাই প্রতিবার searchItems
পুনরায় রেন্ডার করে।
এই সমস্যাটি ঠিক করার জন্য, আপনি ডিপেন্ডেন্সিস হিসেবে পাস করার আগে searchOptions
অবজেক্টটি মেমোইজ করতে পারেন:
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Only changes when text changes
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Only changes when allItems or searchOptions changes
// ...
উপরের উদাহরণে, যদি text
পরিবর্তিত না হয়, তাহলে searchOptions
অবজেক্টটিও পরিবর্তিত হবে না। তবে, আরও ভালো সমাধান হলো searchOptions
অবজেক্টটি useMemo
ফাংশনের ভেতরে সরানো:
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Only changes when allItems or text changes
// ...
এখন আপনার calculation text
এর উপর সরাসরি নির্ভর করে (যা একটি string এবং “আকস্মিকভাবে” পরিবর্তিত হতে পারবে না)।
একটি ফাংশন মেমোইজ করা
ধরুন Form
কম্পোনেন্টটি memo
. দ্বারা মোড়ানো। আপনি এটির মধ্যে একটি ফাংশনকে prop হিসেবে পাস করতে চান:
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
}
যেমন {}
একটি আলাদা অবজেক্ট তৈরি করে, ফাংশন যেমন function() {}
এবং এক্সপ্রেশন () => {}
প্রতিটি রি-রেন্ডারে আলাদা ফাংশন তৈরি করে। নিজে নিজে, একটি নতুন ফাংশন তৈরি করা একটি সমস্যা নয়। এটি এড়িয়ে চলার মতো কিছু নয়! তবে, যদি Form কম্পোনেন্টটি মেমোইজ করা হয়, ধরে নেওয়া হয় যে কোনো props পরিবর্তিত হবে না তাহলে এটি রি-রেন্ডার করবে না। কিন্তু যদি একটি prop সর্বদা ও আলাদা হয়, তাহলে মেমোইজেশনের উদ্দেশ্য নষ্ট হয়ে যাবে।
একটি ফাংশনকে useMemo
দিয়ে মেমোইজ করতে হয়, ক্যাল্কুলেটেড ফাংশনটি আরেকটি ফাংশন ফেরত দেবে:
export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
এটি খানিকটা বেমানান মনে হচ্ছে! ফাংশনগুলি মেমোইজ করা যথেষ্ট সাধারণ, এবং React এর জন্য একটি নির্মিত হুক রয়েছে যা বিশেষভাবে এই উদ্দেশ্যের জন্য। useMemo
-এর পরিবর্তে আপনার ফাংশনগুলিকে useCallback
ব্যবহার করুন একটি অতিরিক্ত নেস্টেড ফাংশন লেখার প্রয়োজন হবে না।
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
উপরের উদাহরণ দুটি সম্পূর্ণরূপে সমতুল্য। useCallback
-এর একমাত্র সুবিধা হল এটি আপনাকে একটি অতিরিক্ত নেস্টেড ফাংশন লেখার প্রয়োজন পড়বে না। এটি অন্য কিছু করে না। useCallback সম্পর্কে আরও পড়ুন।
সমস্যা সমাধান
calculation প্রতিটি রি-রেন্ডারে দুইবার রান করে
Strict Mode-এ, React আপনার কিছু ফাংশন একবারের পরিবর্তে দুইবার কল করবে:
function TodoList({ todos, tab }) {
// This component function will run twice for every render.
const visibleTodos = useMemo(() => {
// This calculation will run twice if any of the dependencies change.
return filterTodos(todos, tab);
}, [todos, tab]);
// ...
এটি প্রত্যাশিত এবং আপনার কোডের কোন সমস্যা সৃষ্টি করবে না।
এই শুধুমাত্র ডেভেলপমেন্ট এর সময় এটি আপনাকে কম্পোনেন্টগুলি পিওর রাখতে সাহায্য করে। React একটি কলের ফলাফল ব্যবহার করে এবং অন্য কলের ফলাফল উপেক্ষা করে। যতক্ষণ আপনার কম্পোনেন্ট এবং calculation ফাংশনগুলি পিওর হয়, তাহলে এটি আপনার লজিকে প্রভাব ফেলবে না। তবে, যদি তারা আকস্মিকভাবে ইমপিওর হয়, তাহলে এটি আপনাকে ভুল খুঁজে বের করে ঠিক করতে সাহায্য করে।
উদাহরণস্বরূপ, এই অপিওর ফাংশন একটি array পরিবর্তন করে যা আপনি একটি prop হিসেবে পেয়েছেন:
const visibleTodos = useMemo(() => {
// 🚩 Mistake: mutating a prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);
React আপনার ফাংশনটি দুইবার কল করে, তাই আপনি লক্ষ্য করবেন যে todo
দুইবার যোগ করা হয়েছে। আপনার calculation কোনো বিদ্যমান অবজেক্ট পরিবর্তন করা উচিত নয়, তবে calculation এর সময় আপনি যে কোনো নতুন অবজেক্ট পরিবর্তন করা ঠিক আছে। উদাহরণস্বরূপ, যদি filterTodos
ফাংশন সবসময় একটি আলাদা array ফেরত দেয়, তাহলে আপনি সেই array পরিবর্তন করতে পারেন:
const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correct: mutating an object you created during the calculation
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);
পিওরিটি সম্পর্কে আরও জানতে কম্পোনেন্টগুলি পিওর রাখা পড়ুন।
এছাড়াও, মিউটেশন ছাড়া অবজেক্ট আপডেট করা এবং array আপডেট করা সম্পর্কে গাইডগুলি দেখুন।
আমার useMemo
কলটি একটি অবজেক্ট ফেরত দেওয়ার কথা, কিন্তু এটি undefined
ফেরত দেয়
এই কোডটি কাজ করে না:
// 🔴 You can't return an object from an arrow function with () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);
জাভাস্ক্রিপ্টে, () => {
এরো ফাংশনের বডি শুরু করে, তাই {
ব্রেসটি আপনার অবজেক্টের অংশ নয়। এই কারণেই এটি কোনো অবজেক্ট ফেরত দেয় না, এবং ভুলের সৃষ্টি করে। আপনি এটি ঠিক করতে পারেন ({
এবং })
এর মতো প্যারেন্থেসিস যোগ করে:
// This works, but is easy for someone to break again
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);
তবে, এটি এখনও বিভ্রান্তিকর এবং কেউ প্যারেন্থেসিস সরিয়ে দিয়ে এটিকে ভেঙে ফেলার জন্য খুবই সহজ।
এই ভুল এড়াতে, স্পষ্টভাবে একটি return
স্টেটমেন্ট লিখুন:
// ✅ This works and is explicit
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);
প্রতিবার আমার কম্পোনেন্ট রেন্ডার হয়, useMemo
ব্যবহারের পরেও পুনরায় রান করে
নিশ্চিত করুন যে আপনি দ্বিতীয় আর্গুমেন্ট হিসেবে ডিপেন্ডেন্সিস array নির্দিষ্ট করেছেন!
যদি আপনি ডিপেন্ডেন্সিস array ভুলে যায়, useMemo
প্রতিবার calculation পুনরায় চালাবে:
function TodoList({ todos, tab }) {
// 🔴 Recalculates every time: no dependency array
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...
এটি দ্বিতীয় আর্গুমেন্ট হিসেবে ডিপেন্ডেন্সিস array পাস করার সংশোধিত ভার্সন:
function TodoList({ todos, tab }) {
// ✅ Does not recalculate unnecessarily
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
যদি এটি সাহায্য না করে, তাহলে সমস্যাটি হল আপনার অন্তত একটি ডিপেন্ডেন্সিস পূর্ববর্তী রেন্ডারের থেকে ভিন্ন। আপনি কনসোলে ম্যানুয়ালি আপনার ডিপেন্ডেন্সিসগুলি লগ করে এই সমস্যাটি ডিবাগ করতে পারেন:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
তারপর আপনি কনসোলে ভিন্ন রি-রেন্ডারের array গুলির উপর রাইট-ক্লিক করে “Store as a global variable”
নির্বাচন করতে পারেন উভয়ের জন্য। ধরুন প্রথমটি temp1
হিসেবে এবং দ্বিতীয়টি temp2
হিসেবে সংরক্ষিত হয়েছে, তাহলে আপনি browser এর কনসোল ব্যবহার করে পরীক্ষা করতে পারেন যে উভয় array প্রতিটি ডিপেন্ডেন্সিস একই কিনা:
Object.is(temp1[0], temp2[0]); // Is the first dependency the same between the arrays?
Object.is(temp1[1], temp2[1]); // Is the second dependency the same between the arrays?
Object.is(temp1[2], temp2[2]); // ... and so on for every dependency ...
যখন আপনি সনাক্ত করেন কোন ডিপেন্ডেন্সিস মেমোইজেশনকে ভঙ্গ করতেছে, তখন এটিকে সরানোর একটি উপায় খুঁজুন, অথবা এটিও মেমোইজ করুন।
আমাকে প্রতিটি লিস্ট আইটেমের জন্য একটি লুপে useMemo
কল করতে হবে, কিন্তু এটি অনুমোদিত নয়
ধরুন Chart
কম্পোনেন্টটি memo
-এ মোড়ানো। আপনি চান ReportList
কম্পোনেন্ট পুনরায় রেন্ডার হলে লিস্টে প্রতিটি Chart
রি-রেন্ডারিং না হক। তবে, আপনি একটি লুপে মদ্ধে useMemo
কল করতে পারবেন না:
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 You can't call useMemo in a loop like this:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}
এর পরিবর্তে, প্রতিটি আইটেমের জন্য একটি কম্পোনেন্ট পৃথক করুন এবং প্রতিটি আইটেমের ডেটাকে মেমোইজ করুন:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Call useMemo at the top level:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}
বিকল্প হিসেবে, আপনি useMemo
সরিয়ে ফেলে Report
-কে memo
. দিয়ে মোড়াতে পারেন। এর ফলে যদি item এর prop পরিবর্তিত না হয়, তাহলে Report
রি-রেন্ডারিং করেবে না, সুতরাং Chart
ও রি-রেন্ডারিং এড়িয়ে যাবে:
function ReportList({ items }) {
// ...
}
const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});