Lecture 57 | Understand Application States - Importance of Redux

Lecture 57 | Understand Application States - Importance of Redux

Introduction

আমাদের অ্যাপ্লিকেশনে কিছু সমস্যা হচ্ছে বুঝতে পারছি। সেই সমস্যাগুলো কেন হচ্ছে, সমস্যার সমাধান কিভাবে হতে পারে সেগুলো নিয়ে এই লেকচারে আলোচনা করা হবে।

Context API

প্রথমে আমরা নিচের কম্পোনেন্ট ট্রী একটু খেয়াল করি।

আমরা জানি আমাদের রুট ফাইল হলো App । এই অ্যাপের আন্ডারে কিছু Page আছে। সেই পেইজের আবার কিছু কম্পোনেন্ট থাকতে পারে। সেই কম্পোনেন্টগুলোর আবার কিছু চাইল্ড কম্পোনেন্ট থাকতে পারে। আবার সেই চাইল্ড কম্পোনেন্টেরও কিছু চাইল্ড কম্পোনেন্ট থাকতে পারে। এখন যদি আমার কোনো একটা কম্পোনেন্টের কোনো ডাটা অন্য কোনো পেইজে দরকার হয় তবে আমরা জানি আমাদের স্টেট আমরা অ্যাপে রাখবো। সব ডাটা গিয়ে অ্যাপে জমা হবে এবং পরবর্তীতে যে জায়গায় দরকার সেই জায়গায় অ্যাপ সেই ডাটা সরবরাহ করবে। কিন্তু সেক্ষেত্রে একটা সমস্যা হলো সব কম্পোনেন্টের মধ্যে প্রপ্স পাস করে যেতে হয়। এখন আমার যদি ৮ লেভেল পর্যন্ত কম্পোনেন্ট ট্রী থাকে তবে সে ৮ লেভেল উপর পর্যন্ত আমাদের প্রপ্স পাস করে যেতে হয়। অনেক সময় সেই প্রপ্স নিয়ে কাজ করা আমাদের জন্য কঠিন হয়ে যায়। সেই সমস্যা থেকে উত্তোরণের জন্য রিয়্যাক্ট আমাদের সল্যুশন দিয়েছে Context API । এর মাধ্যমে আমরা যে সুবিধাটা পাই তা হলো একবার যদি আমরা আমাদের মতো করে কনটেক্সট এপিআই সেট করে ফেলতে পারি তাহলে আমাদের আর প্রত্যেক কম্পোনেন্টের মধ্যে গিয়ে প্রপ্স পাস করতে হবে না। আমাদের যে জায়গায় দরকার সেই জায়গা থেকেই ঐ কনটেক্সট এপিআই কনজিউম করতে পারবো।

কিন্তু ছোটখাট কাজের জন্য এই কনটেক্সট এপিআই অনেক ভাল। রিয়্যাক্ট আমাদের বলে দিয়েছে যদি কোনো কমপ্লেক্স কাজ হয় তবে সরাসরি কনটেক্সট এপিআই ব্যবহার করাটা সঠিক হবে না। কারণ কনটেক্সট এপিআই ব্যবহার করে আমরা যে লজিকগুলো লিখবো সেই লজিকগুলো মেইনটেইন করাটা আবার কষ্টসাধ্য হয়ে যাবে। আসলে বড় বা কমপ্লেক্স কাজের জন্য আমরা আল্টিমেটলি কনটেক্সট এপিআই ব্যবহার করে থাকি। কিন্তু একটু ভিন্ন আঙ্গিকে। সেটা কিভাবে? কনটেক্সট এপিআইয়ের মাধ্যমে আমরা অ্যাপ্লিকেশনের পুরো একটা স্টেট সেট করে নিই প্রথমে। এইক্ষেত্রে আমরা আমাদের অ্যাপ্লিকেশনকে দুই ভাগ করে নিবো। আমরা আলাদাভাবে স্টেট নিবো। এবং এই স্টেটের ডাটা আমাদের মূল অ্যাপ্লিকেশনের সাথে থাকবে না। হতে পারে আমাদের একটা ফর্ম আছে। সেই ফর্ম ম্যানেজ করার জন্য কোনো কাস্টম হুক প্রয়োজন হতে পারে। সেটা সেই কম্পোনেন্টের নিজস্ব ব্যাপার। সেটার সাথে অ্যাপ্লিকেশন লেভেলের ডাটার কোনো সম্পর্ক নেই। এখন প্রশ্ন হলো তাহলে অ্যাপ্লিকেশন লেভেলের ডাটা কোনগুলো? ধরেন আপনার একটা ই-কমার্স সাইট আছে। সেখানে ১০০টা প্রোডাক্ট আছে, ২০টা ভ্যারিয়েন্ট আছে। এগুলো সার্ভারেও থাকবে, আবার ইউজারকে শো করানোর জন্য ফ্রন্টএন্ডে বা ক্লায়েন্টের কাছেও থাকতে হবে। এই ডাটাগুলো হলো অ্যাপ্লিকেশন লেভেলের ডাটা। কম্পোনেন্ট লেভেলে যে স্টেট লাগবে সেগুলো আমরা কম্পোনেন্টের মধ্যেই ডিক্লেয়ার করবো। কিন্তু অ্যাপ্লিকেশন লেভেলে যে ডাটা লাগবে সেগুলো আমরা ডিক্লেয়ার করবো ঐ স্টেটের মধ্যে বা ডাটা লেয়ারের মধ্যে। ডাটা লেয়ারকে আমরা সবসময় আলাদা করে রাখবো। এখানে মূলত থাকবে একটা Immutable Object। কারণ রিয়্যাক্ট মিউটেবল অবজেক্ট ট্র্যাক করতে পারে না। মিউটেবল নিয়ে রিয়্যাক্টে কাজই করা যায় না। তাই ইমমিউটেবল অবজেক্ট রাখা হয়। যখন কোনো চেইঞ্জ আসবে তখন এই অবজেক্টটা ক্লোন হয়ে আপডেটেড আরেকটা রেফারেন্স তৈরি করে। যখন আগের অবজেক্ট আর আপডেটেড অবজেক্ট কমপেয়ার করে false পাবো, তখন ডাটা লেয়ার কনটেক্সট এপিআই কে বলবে ডাটা চেইঞ্জ হয়েছে। সেই সিগন্যাল পেয়ে কনটেক্সট এপিআই তার ডাটা বা স্টেট চেইঞ্জ করে নিবে। এর সাথে সাথে যে কম্পোনেন্ট সেই ডাটা ব্যবহার করছে সে খুব সহজেই তার ডাটা আপডেট করে নিতে পারবে।

এখন প্রশ্ন হচ্ছে আমাদের কাছে ডাটা আছে, আমাদের কাছে কনটেক্সট এপিআই আছে। আমরা ডাটাগুলোকে পরিবর্তন করবো কিভাবে? এক্ষেত্রে আমাদের দুইটা লেয়ার লাগবে। লজিক্যাল লেয়ার এবং নেটওয়ার্ক লেয়ার। ডাটা আপডেটের যতো লজিক এবং ফাংশনালিটিজ আছে, আমরা সব লিখবো লজিক্যাল লেয়ারের মধ্যে। আর এপিআই সার্ভারের সাথে কমিউনিকেশন করার জন্য যে সমস্ত ফাংশনালিটিজ আছে সেগুলো আমরা লিখবো নেটওয়ার্ক লেয়ারের মধ্যে। নিচের ডায়াগ্রামে আপনারা বিষয়টা ভালভাবে বুঝতে পারবেন।

আমরা আমাদের অ্যাপ্লিকেশনকে মোট দুইটা ভাগে ভাগ করে ফেলেছি। উপরের ভাগে আমরা আমাদের সমস্ত বিজনেস লজিক লিখবো। এবং নিচের লেয়ারে আমরা আমাদের UI নিয়ে কাজ করবো। এখন এই যে আমরা বিজনেস লজিককে আলাদা করলাম এটা করার কারণে আমরা কি সুবিধা পাবো? সেটা বর্ণনা করছি।

ধরেন আপনি একটা স্টার্টআপ শুরু করেছেন। প্রথমে আপনি ভাবলেন ওয়েব অ্যাপ্লিকেশন দিয়ে শুরু করবেন। শুরু করলেন রিয়্যাক্টের মাধ্যমে ওয়েব অ্যাপ্লিকেশন বানিয়ে। কিছুদিন পর দেখলে আপনার বিজনেস অনেক গ্রো করেছে। এখন ইউজারদের জন্য একটা মোবাইল অ্যাপ্লিকেশন হলে ভাল হয়। সেক্ষেত্রে আপনি বানিয়ে ফেললেন একটা মোবাইল অ্যাপ্লিকেশন রিয়্যাক্ট ন্যাটিভের মাধ্যমে। কিছুদিন পর বিজনেস এত ভাল লেভেলে চলে গেলো যে একটা ডেস্কটপ অ্যাপ্লিকেশন সবাই ডিমান্ড করছে। সেক্ষেত্রে আপনি ইলেক্ট্রন জেএসের মাধ্যমে একটা ডেস্কটপ অ্যাপ্লিকেশন বানিয়ে ফেললেন। এখানে খেয়াল করুন আপনার UI বানানোর জন্য আপনাকে প্রেজেন্টেশন লেয়ারে পরিবর্তন করতে হলেও আপনার বিজনেস লজিক কিন্তু একই থেকে যাচ্ছে। বিজনেস লজিকের কিন্তু কোনো পরিবর্তন হচ্ছে না। তাই বারবার বিজনেস লজিক আপনাকে লিখতে হচ্ছে না।

এখন প্রশ্ন হচ্ছে আমরা এই লজিক্যাল বা নেটওয়ার্ক লেয়ারকে হ্যান্ডেল করবো কিভাবে বা কিভাবে আমরা আমাদের বিজনেস লজিকের ডাটা হ্যান্ডেল করবো? এর অনেকগুলো উপায় আছে। প্রথম উপায় হচ্ছে useReducer হুক ব্যবহার করে। তবে আমরা যে জায়গায় useReducer নিবো স্টেট শুধু ঐ কম্পোনেন্টের মধ্যেই সীমাবদ্ধ থাকবে। সেটা কখনই গ্লোবাল হবে না। গ্লোবাল হওয়ার জন্য আমাদের দরকার কনটেক্সট এপিআই। এখন তাহলে প্রশ্ন উঠতে পারে, তাহলে শুধু শুধু রিডিউসার ব্যবহার করার দরকার কি? আমরা যেখানে কনটেক্সট প্রোভাইডার নিচ্ছি সেখানে একটা স্টেট নিয়ে নিলেই তো হয়ে যায়। হ্যাঁ তা ঠিক। তবে স্টেট প্রিমিটিভ ডাটার জন্য খুবই ভাল। কিন্তু যখন একটা কমপ্লেক্স অবজেক্ট হ্যান্ডেল করতে হবে তখন স্টেট অনেক জঘন্য একটা জিনিস। সে কারণে আমাদেরকে রিডিউসার ব্যবহার করতে হবে। রিডিউসার ব্যবহার করে খুব সহজেই switch কেস বা কন্ডিশনের মাধ্যমে সহজেই স্টেট হ্যান্ডেল করতে পারি।

Reducer হলো একটি ফাংশন যার আর্গুমেন্ট হিসেবে থাকে একটি স্টেট এবন একটি অ্যাকশন। অ্যাকশন হলো একটি অবজেক্ট। যার মধ্যে থাকে type এবং payload। টাইপ হলো আমরা যে যে কাজগুলো করবো তার একটা নাম। নিচের কোডটি দেখলে আপনারা আরো ভালভাবে বুঝতে পারবেন।

const INIT_STATE = {
    playlists: {},
    recents: [],
    favorites: [],
};

const reducer = (state=INIT_STATE, action) => {
    switch(action.type) {
        case 'fetch_playlist':
            // logic
            break;
        case 'add_to_recents':
            // logic
            break;
}

রিডিউসারের মাধ্যমে আমরা ছোট ছোট কাজগুলো ভেঙ্গে ভেঙ্গে করতে পারছি। কিন্তু যদি স্টেটের মাধ্যমে করতাম তাহলে আমাদের আগের সমস্ত ডাটা {...prev} করে করে রাখতে হতো। আমাদের অবজেক্ট যদি ৪/৫ লেয়ার নেস্টেড হয় তাহলে সেটা মেইনটেইন করা খুবই কষ্ট হয়ে পড়ে। ছোটখাট অ্যাপ্লিকেশনে useReducer এবং useContext কম্বাইন করে একটা সিস্টেম তৈরি করে স্টেট ম্যানেজ করা হয়।

এখন এই useReducer এর একটা সমস্যা আছে। ধরেন আমাদের অ্যাপ্লিকেশনে ৪০/৫০ রকমের ডাটা আছে। এখন সবগুলোর জন্য যদি আমরা একটা রিডিউসার ব্যবহার করি তাহলে সেটা মেইন্টেইন করা কষ্টসাধ্য হয়ে যাবে। আমাদের প্রতিটা ডাটা বা এন্টিটির জন্য আলাদা আলাদা রিডিউসার বানাতে হবে। এবার প্রতিটা রিডিউসারের জন্য আলাদা আলাদা কনটেক্সট বানাতে হবে। সেগুলোর প্রোভাইডার আমাদের অ্যাপ্লিকেশনে পাস করতে হবে। এভাবে দেখা যাবে ৪০/৫০টা প্রোভাইডার প্রত্যেকে প্রত্যেকের চাইল্ড হয়ে বিশাল আকার ধারণ করছে। সেটা দেখতেও ভাল না, মেইনটেনেবলও না। এটা থেকে বাঁচার অনেকগুলো উপায় আছে।

উপায়গুলো হচ্ছে -

এবার আমরা আমাদের ডাটা, লজিক্যাল লেয়ার এবং নেটওয়ার্ক লেয়ার সবগুলোকে একসাথে রিডাক্স হিসেবে ধরে নিলাম। এখন আমরা কিভাবে ডাটার এক্সেস নিতে পারি বা কিভাবে ডাটা আপডেট করতে পারি তা শিখবো। আমরা প্রথমে নিচের ডায়াগ্রামটা খেয়াল করি।

আমাদের যে বিজনেস লজিকের ভাগ সেটাকে আমরা ধরলাম Redux, এবং প্রেজেন্টেশন লেয়ারকে ধরে নিলাম React। এখন রিডাক্স আর রিয়্যাক্টের মধ্যে কমিউনিকেশন করার জন্য আমাদের এদের মাঝখানে আরেকটা জিনিস বা টুল লাগবে, সেটা হলো React-Redux। রিয়্যাক্ট-রিডাক্স অ্যাডাপ্টর প্যাটার্নের মতো কাজ করে। অ্যাডাপ্টর প্যাটার্ন হলো ধরেন আপনি একটা লেয়ারের কোড একভাবে লিখলেন। সেটাকে অন্য লেয়ারে পাঠাতে হলে ঐ লেয়ারের মতো করে পাঠাতে হবে। সেই কাজটা যে জায়গায় হচ্ছে সেটাকে আমরা বলি অ্যাডাপ্টর। আর এভাবে কাজ করাকে বলে অ্যাডাপ্টর প্যাটার্ন। রিয়্যাক্ট-রিডাক্স রিয়্যাক্টের সাথে কনটেক্সট এপিআইয়ের মাধ্যমে কমিউনিকেট করবে। কিন্তু রিডাক্সের সাথে কিভাবে কমিউনিকেট করবে। সেটা একটু জানার চেষ্টা করি।

রিডাক্সের সাথে কমিউনিকেট করার জন্য আমাদের দুইটা জিনিস লাগবে। Selector এবং Dispatch। সিলেক্টরের মাধ্যমে আমরা এত বড় স্টোর থেকে কোন ডাটা লাগবে সেটা সিলেক্ট করে রাখবো। এবং রিয়্যাক্ট থেকে কোন ডাটা আমাদের স্টোরে সেইভ করতে হবে তা স্টোর পর্যন্ত পৌঁছানোর জন্য আমরা ডিসপ্যাচ ব্যবহার করবো।

ধরেন ইউটিউবে অসংখ্য চ্যানেল আছে। আমরা আমাদের পছন্দের চ্যানেলকে সিলেক্ট করে সাবস্ক্রাইব করে রাখলে যদি বেল আইকনে ক্লিক করে রাখি তবে ঐ চ্যানেলের সমস্ত ভিডিও আপলোড হওয়ার সাথে সাথে আমাদের কাছে নোটিফিকেশন চলে আসে। এটা সিলেক্টর হিসেবে ধরে নেন। যখনই আমরা কিছু সিলেক্ট করছি সেখানে একটা সাবস্ক্রিপশন হয়। আবার যখন কোনো ভিডিওতে আমরা লাইক, কমেন্ট করি সেটা চ্যানেলের অ্যাডমিনের কাছে পৌঁছাচ্ছে। এটাকে ধরে নেন ডিসপ্যাচ। সহজ কথায় সিলেক্টর হলো ডাটা রীড করার জন্য এবং ডিসপ্যাচ হলো ডাটা ক্রিয়েট, আপডেট বা ডিলিট করার জন্য। আমরা নিচের ডায়াগ্রামটা খেয়াল করলে সহজেই রিডাক্স বুঝতে পারবো।

আমাদের একটা স্টোর থাকবে। সেই স্টোরটা হবে ইমমিউটেবল অবজেক্ট। সেখান থেকে আমাদের রিয়্যাক্টে যে ডাটা লাগবে তা সিলেক্টরের মাধ্যমে সাবস্ক্রাইব করে রিয়্যাক্টে যাবে। এখন শুধু গেলেই তো হবে না। রিয়্যাক্ট কম্পোনেন্টে ইউজার কিছু আপডেট, ক্রিয়েট বা ডিলিট করতে পারে। অর্থাৎ অ্যাকশন ক্রিয়েট করবে। সেটা অ্যাকশন ক্রিয়েটরের মাধ্যমে ডিসপ্যাচ হবে। অ্যাকশন হলো একটা ম্যাসেজ যা আমাদের স্টোরে পৌঁছাতে হবে। ডিসপ্যাচ হয়ে তা যাবে রিডিউসারের কাছে। রিডিউসার এবার সিদ্ধান্ত নিবে অ্যাকশন অনুযায়ী সে ডাটা আপডেট করবে কি করবে না। যখন সে দেখবে ইমেইল পাঠাতে বলা হয়েছে। তখন সেই ইমেইলটা পাঠিয়ে দিয়ে তার ডাটা স্টোরে আপডেট করে দিবে। স্টোর সেই ডাটা সিলেক্টরের মাধ্যমে আগের কম্পোনেন্টে পাঠিয়ে আপডেট করে দিবে। এভাবে এই চক্র চলতে থাকে। এখানে প্রচুর রিপিটিটিভ কাজ আছে। যা আমরা মিডলওয়্যারের মাধ্যমে সহজ করতে পারি। কিন্তু এই মুহূর্তে মিডলওয়্যার বুঝা যাবে না। আগে আমাদের রিডাক্স বুঝতে হবে ভাল করে। আপাতত এটাই রিডাক্সের কনসেপ্ট।

Conclusion

এই ক্লাসটা খুব ভালভাবে আপনারা স্টাডি করবেন। এই ক্লাস ভাল করে বুঝলে যেকোনো স্টেট ম্যানেজমেন্ট টুল নিয়ে কাজ করা সহজ হয়ে যাবে।