প্রত্যেক React ডেভেলপারকেই শেষ পর্যন্ত একই প্রশ্নের মুখোমুখি হতে হয়: আমি কি Context ব্যবহার করব, নাকি এটি একটি Redux-এর সমস্যা? আপনি যদি মাত্র কয়েক মাস ধরে কাজ করে থাকেন, তবে অনলাইনে এমন অনেক আলোচনা পাবেন যা এটিকে একটি 'হয় এটি, না হয় ওটি' সিদ্ধান্তের মতো উপস্থাপন করে। কিছু টিউটোরিয়াল Redux-কে একটি পুরানো বোঝা (legacy baggage) হিসেবে গণ্য করে। আবার অন্যরা সতর্ক করে যে, Context দিয়ে একটি to-do লিস্টের বেশি কিছু করা সম্ভব নয়। এই দুই চরমপন্থার কোনটিই খুব একটা সহায়ক নয়। আসল সত্য হলো, এই টুলগুলো ভিন্ন ধরনের সমস্যা সমাধান করে, এবং আপনার অ্যাপ্লিকেশনে আসলে কী ঘটছে তার ওপর ভিত্তি করেই সঠিকটি বেছে নিতে হয়।
Prop Drilling-এর সমস্যা
কোনো state management কৌশল বেছে নেওয়ার আগে, এই দুটি টুল আসলে কোন সমস্যাটি সমাধান করার চেষ্টা করছে তা বোঝা প্রয়োজন। কল্পনা করুন আপনি একটি ই-কমার্স সাইট তৈরি করছেন। আপনি একদম উপরের লেভেলের App কম্পোনেন্টে ইউজারের প্রোফাইলটি ফেচ (fetch) করছেন। নিচের ফুটার (footer)-এ থাকা একটি ছোট AccountLink কম্পোনেন্টের সেই প্রোফাইল ছবিটির প্রয়োজন। একটি গ্লোবাল স্টোর ছাড়া, user অবজেক্টটিকে Home, তারপর Header, তারপর NavContainer, তারপর UserDropdown এবং সবশেষে AccountLink-এর মধ্য দিয়ে যেতে হবে। মাঝখানের প্রতিটি লেয়ার এমন ডেটা স্পর্শ করে যা তাদের প্রয়োজন নেই। এটাই হলো prop drilling।
Prop drilling কম্পোনেন্টগুলোকে ভঙ্গুর (brittle) করে তোলে। রিফ্যাক্টরিং করা ঝুঁকিপূর্ণ হয়ে পড়ে কারণ মাঝখানের একটি অংশ সরিয়ে ফেললে পুরো চেইনটি ভেঙে যায়। কম্পোনেন্টগুলোর পুনরায় ব্যবহারযোগ্যতা (reusability) ক্ষতিগ্রস্ত হয় কারণ কম্পোনেন্টগুলোকে এমন সব props দিতে হয় যা তারা কেবল নিচের দিকে পাস করে। Context এবং Redux উভয়ই দূরবর্তী কম্পোনেন্টগুলোকে সরাসরি শেয়ার করা ডেটা সাবস্ক্রাইব করার সুযোগ দিয়ে এই সমস্যাটি দূর করে। কিন্তু তারা যেভাবে সেই ডেটা সরবরাহ করে এবং এর জন্য যে খরচ বা জটিলতা প্রয়োজন, তাতে দ্রুত পার্থক্য দেখা দেয়।
কখন React Context API সঠিক পছন্দ
React Context লাইব্রেরির মধ্যেই বিল্ট-ইন হিসেবে থাকে। কোনো অতিরিক্ত npm ইনস্টল, কোনো বিল্ড কনফিগারেশন বা কোনো বয়লারপ্লেট (boilerplate) ফাইলের প্রয়োজন নেই। আপনি একটি context অবজেক্ট তৈরি করেন, আপনার ট্রি-র একটি অংশকে একটি Provider দিয়ে র্যাপ (wrap) করেন এবং যেকোনো নেস্টেড কম্পোনেন্টে useContext দিয়ে সেই ভ্যালু ব্যবহার করেন। এই সহজলভ্যতার কারণে, Context ছোট থেকে মাঝারি মানের প্রজেক্টের জন্য চমৎকার কাজ করে যেখানে স্টেট পরিবর্তন খুব কম হয় এবং স্টেটের গঠন তুলনামূলকভাবে ফ্ল্যাট (flat) থাকে।
UI থিমের কথা ভাবুন। একজন ব্যবহারকারী হয়তো প্রতি সেশনে একবার বা দুবার লাইট এবং ডার্ক মোডের মধ্যে পরিবর্তন করেন। এই ভ্যালুটি প্রতিটি styled কম্পোনেন্টে ছড়িয়ে পড়ে, কিন্তু এটি এতই কম পরিবর্তিত হয় যে পারফরম্যান্স নিয়ে চিন্তার খুব একটা অবকাশ থাকে না। অথেন্টিকেশন স্ট্যাটাস (Authentication status) আরেকটি ক্লাসিক উদাহরণ। একবার ব্যবহারকারী লগ-ইন করলে, isAuthenticated ফ্ল্যাগ এবং user অবজেক্টটি ডজন ডজন পেজ নেভিগেশনের মধ্যেও স্থিতিশীল থাকে। ভাষা বা লোকালাইজেশন সেটিংসও একইভাবে কাজ করে। এগুলো হলো বিস্তৃত এবং ধীরগতির সিগন্যাল যা অনেক কম্পোনেন্টের প্রয়োজন হয়, কিন্তু খুব কম কম্পোনেন্ট এগুলো পরিবর্তন করে।
সমস্যাটি হলো Context কীভাবে আপডেট হ্যান্ডেল করে। যখন একটি Context Provider-এর ভ্যালু পরিবর্তিত হয়, React সেই context ব্যবহারকারী প্রতিটি কম্পোনেন্টকে পুনরায় রেন্ডার (re-render) করে। একটি ছোট অ্যাপ্লিকেশনে আপনি এটি টের পাবেন না। কিন্তু একটি বড় অ্যাপ্লিকেশনে, যদি আপনি বহুল ব্যবহৃত কোনো Context-এর ভেতরে দ্রুত পরিবর্তনশীল ডেটা রাখেন, তবে এটি অপ্রয়োজনীয় রেন্ডারের একটি ক্যাসকেড (cascade) তৈরি করবে। আপনি অস্থিরতা (volatility) কমানোর জন্য context গুলোকে আলাদা করতে পারেন, কিন্তু সেই পর্যায়ে আপনি ম্যানুয়ালি অপ্টিমাইজেশনের কাজ করছেন যা অন্য একটি টুল সহজেই সমাধান করতে পারত।
কখন Redux Toolkit-এর প্রয়োজন পড়ে
Redux Toolkit এমন সব অ্যাপ্লিকেশনের জন্য ডিজাইন করা হয়েছে যেখানে state জটিল, আপডেট ঘনঘন হয় এবং একাধিক দূরবর্তী ফিচারের একই ডেটা কোনো সংঘর্ষ ছাড়াই পড়া এবং লেখার প্রয়োজন হয়। একটি শপিং কার্টের কথা চিন্তা করুন। ব্যবহারকারী একটি প্রোডাক্ট কার্ড থেকে একটি আইটেম যোগ করলেন। হেডার-এর কার্ট আইকনটিকে অবশ্যই তার ব্যাজ কাউন্ট আপডেট করতে হবে। একটি সাইডবার বেরিয়ে এসে আইটেমগুলো দেখাবে। একটি ডিসকাউন্ট কোড ইনপুট ভ্যালিডেশন চালাবে। পরবর্তীতে চেকআউট পেজ কার্টের বিষয়বস্তু পড়বে। এই স্টেটটি পুরো ট্রি জুড়ে থাকা সম্পর্কহীন কম্পোনেন্ট দ্বারা স্পর্শ করা হয় এবং এটি প্রায়ই পরিবর্তিত হয়।
Redux Toolkit একটি সেন্ট্রালাইজড স্টোর এবং স্টেট-এর সুনির্দিষ্ট স্লাইস (slices) ব্যবহারের মাধ্যমে এটি সমাধান করে। কম্পোনেন্টগুলো useSelector ব্যবহার করে কেবল তাদের প্রয়োজনীয় ডেটার অংশটুকু সাবস্ক্রাইব করে। যদি রিয়েল-টাইম ড্যাশবোর্ডে স্টকের দাম আপডেট হয়, তবে ইউজার প্রোফাইল সেটিংস প্রদর্শনকারী কম্পোনেন্টটি সক্রিয় হবে না। Redux অভ্যন্তরীণভাবে reference equality চেক ব্যবহার করে যাতে সাবস্ক্রিপশনগুলো সুনির্দিষ্ট (granular) হয়। যখন আপনার কম্পোনেন্টের সংখ্যা শত শত ছাড়িয়ে যায়, তখন এটি অত্যন্ত গুরুত্বপূর্ণ হয়ে ওঠে।
Redux আপনাকে একটি প্রেডিক্টেবল (predictable) ডেটা ফ্লো প্রদান করে। স্টেট পরিবর্তন ঘটে ডিসপ্যাচ করা অ্যাকশন (dispatched actions) এর মাধ্যমে যা রিডিউসার (reducers) দ্বারা হ্যান্ডেল করা হয়। এটি শুনতে কিছুটা জটিল মনে হতে পারে, কিন্তু বাস্তবে এর মানে হলো আপনি আপনার কোডবেসে addToCart লিখে সার্চ করলেই কার্ট পরিবর্তনকারী প্রতিটি কোড পাথ খুঁজে পাবেন। একটি বড় টিমের ক্ষেত্রে, এই নিয়মটি বাগ (bug) প্রতিরোধ করে। অন্যদিকে, Context হলো কেবল একটি ভ্যালু এবং একটি সেটার (setter)। যেকোনো কনজিউমার setState কল করতে পারে, এবং একটি ভুল ভ্যালুর উৎস খুঁজে বের করতে হলে আপনাকে একাধিক কম্পোনেন্টে ব্রেকপয়েন্ট (breakpoint) সেট করতে হবে।
যেখানে তারা প্রকৃত অর্থে আলাদা হয়
পারফরম্যান্স বৈশিষ্ট্য (Performance characteristics) অন্য যেকোনো কিছুর চেয়ে এই টুলগুলোকে আলাদা করে। Context কোনো শর্ত ছাড়াই সকল কনজিউমারদের কাছে নতুন ভ্যালু ব্রডকাস্ট করে। Redux শুধুমাত্র সেই সাবস্ক্রাইবারদের নোটিফাই করে যাদের নির্বাচিত স্লাইস (slice) পরিবর্তিত হয়েছে। আপনি যদি একটি রিয়েল-টাইম স্টক ড্যাশবোর্ড তৈরি করেন যেখানে প্রতি সেকেন্ডে কোটেশন রিফ্রেশ হয়, তবে Context একটি গ্লোবাল রেন্ডার স্টর্ম (global re-render storm) তৈরি করবে। Redux শুধুমাত্র টিকার সেল (ticker cell) এবং স্পার্কলাইন চার্টটিকে পুনরায় গণনা (recompute) করতে দেবে।
ডিবাগিং (Debugging) হলো আরেকটি ক্ষেত্র যেখানে জটিল অ্যাপ্লিকেশনের ক্ষেত্রে Redux এগিয়ে থাকে। Redux DevTools আপনাকে টাইম-ট্রাভেল ডিবাগিং সুবিধা দেয়। আপনি প্রতিটি ডিসপ্যাচ করা অ্যাকশনের (dispatched action) মাধ্যমে পেছনের দিকে যেতে পারেন এবং স্টেট রিওয়াইন্ড (state rewind) হতে দেখতে পারেন। শিপিং ক্যালকুলেশন, পেমেন্ট ভ্যালিডেশন এবং এরর রিকভারি সহ একটি মাল্টি-স্টেপ চেকআউট ফ্লো-তে, একটি বাগের কারণ হওয়া সঠিক সিকোয়েন্সটি পুনরায় প্লে করতে পারা অত্যন্ত মূল্যবান। Context স্ট্যান্ডার্ড React DevTools-এর ওপর নির্ভর করে। আপনি বর্তমান context ভ্যালুগুলো পরিদর্শন করতে পারেন, কিন্তু এতে কোনো বিল্ট-ইন অ্যাকশন লগ বা স্টেট ডিফ ভিউয়ার (state diff viewer) নেই। আপনাকে আবার কনসোল লগ (console logs) ব্যবহার করতে হবে।
মিডলওয়্যার এবং সাইড ইফেক্টস (Middleware and side effects) হলো Redux-এর অবিচ্ছেদ্য অংশ। Redux Toolkit-এ createAsyncThunk অন্তর্ভুক্ত রয়েছে এবং এটি ডেটা-ফেচিং লাইব্রেরিগুলোর সাথে সুন্দরভাবে ইন্টিগ্রেট হয়। আপনি Redux ডেটা ফ্লো-এর মধ্যেই একটি API কল পরিচালনা করতে পারেন, একটি লোডিং স্পিনার দেখাতে পারেন, নেটওয়ার্ক ফেইলিউর হ্যান্ডেল করতে পারেন এবং ফলাফল ক্যাশ (cache) করতে পারেন। Context-এ অ্যাসিনক্রোনাস লজিকের জন্য কোনো বিল্ট-ইন প্যাটার্ন নেই। আপনি হয় কম্পোনেন্টের ভেতরে ডেটা ফেচ করবেন এবং তারপর ফলাফলটি Context-এ পুশ করবেন, অথবা আপনি প্রোভাইডারগুলোকে নিজস্ব তৈরি করা অ্যাসিনক্রোনাস ইউটিলিটি দিয়ে র্যাপ (wrap) করবেন। এটি কাজ করে, কিন্তু এটি অ্যাড-হক (ad hoc)।
সেটআপ খরচ (Setup cost) এর ক্ষেত্রে Context স্পষ্টভাবে জয়ী হয়। একটি থিম প্রোভাইডার তৈরি করতে প্রায় পাঁচ মিনিট সময় লাগে। Redux Toolkit-এর জন্য একটি স্টোর ফাইল তৈরি করা, স্লাইসগুলো সংজ্ঞায়িত করা এবং আপনার অ্যাপ্লিকেশনকে একটি Provider দিয়ে র্যাপ করা প্রয়োজন। পুরনো Redux এবং এর বিশাল পরিমাণ বয়লারপ্লেটের (boilerplate) মতো এটি এখন আর সপ্তাহব্যাপী আনুষ্ঠানিকতা নয়, তবুও এটি Context-এর তুলনায় বেশি সেটআপের প্রয়োজন হয়। একটি উইকএন্ড সাইড প্রজেক্ট বা তিনটি রাউট বিশিষ্ট ড্যাশবোর্ডের জন্য এই অতিরিক্ত ঝামেলা (overhead) হয়তো প্রয়োজন নেই।
একই অ্যাপ্লিকেশনে উভয়ই ব্যবহার করা
আপনাকে যেকোনো একটি পক্ষ বেছে নিতেই হবে এমন কোনো কথা নেই। অনেক প্রোডাকশন অ্যাপ্লিকেশন গ্লোবাল UI শেল সংক্রান্ত কাজের জন্য Context এবং ডোমেইন-ভারী বিজনেস ডেটার জন্য Redux ব্যবহার করে। একটি সাধারণ প্যাটার্ন হলো থিম, লোকাল (locale) এবং সম্ভবত একটি হালকা অথ ফ্ল্যাগ (auth flag) Context-এ রাখা, কারণ প্রতিটি রাউটের এগুলো প্রয়োজন এবং এগুলো খুব কম পরিবর্তিত হয়। অন্যদিকে, অর্ডার ম্যানেজমেন্ট সিস্টেম, নোটিফিকেশন সেন্টার এবং ডেটা টেবিলগুলো Redux-এ থাকে যেখানে ঘন ঘন আপডেট এবং ক্রস-কম্পোনেন্ট লজিকের জন্য সুনির্দিষ্ট নিয়ন্ত্রণের প্রয়োজন হয়।
এই হাইব্রিড পদ্ধতিটি একটি স্ট্যাটিক থিম অবজেক্টের চারপাশে সম্পূর্ণ Redux স্টোর চাপিয়ে না দিয়েই সহজ কাজগুলোকে সহজ রাখে। এটি আপনার Redux স্লাইসগুলোকে এমন UI ক্রোম (UI chrome) দিয়ে পূর্ণ হওয়া থেকেও রক্ষা করে যার আসলে ইন্ডাস্ট্রিয়াল-গ্রেড স্টেট ম্যানেজমেন্টের প্রয়োজন ছিল না।
আসল সারসংক্ষেপ (The Real Takeaway)
ভারী টুল বেছে নেওয়ার মধ্যে কোনো সম্মানের বিষয় নেই। আপনার স্টেট কত ঘন ঘন পরিবর্তিত হয়, কতগুলো কম্পোনেন্ট এটি ব্যবহার করে এবং টিমের সীমানা ছাড়িয়ে মিউটেশনগুলো ট্র্যাক করার প্রয়োজন আছে কি না, তা দেখে শুরু করুন। আপনি যদি মাঝারি আকারের একটি অ্যাপে ধীরগতির এবং ব্যাপকভাবে শেয়ার করা ভ্যালুগুলো পরিচালনা করেন, তবে Context সম্ভবত যথেষ্ট। যদি আপনার স্টেট ঘন ঘন পরিবর্তিত হয়, সম্পর্কহীন ফিচারগুলোতে বিস্তৃত থাকে এবং একটি পরিষ্কার অডিট ট্রেইল (audit trail) প্রয়োজন হয়, তবে Redux Toolkit আপনার কষ্ট কমাবে।
কনফারেন্স টক বা GitHub স্টার দেখে নয়, বরং আপনার প্রজেক্টের ধরন অনুযায়ী বেছে নিন। পঞ্চাশটি আইটেম থাকা একটি শপিং কার্ট মানেই যে স্বয়ংক্রিয়ভাবে Redux প্রয়োজন হবে তা নয়, আবার একটি থিম টগল করার জন্য গ্লোবাল স্টোরের প্রয়োজন নেই। সমস্যার সাথে সঠিক টুলটি মেলান, তাহলে হাইপ সাইকেল (hype cycle) শেষ হওয়ার অনেক পরেও আপনার কোডবেস রক্ষণাবেক্ষণযোগ্য থাকবে।
