পাঠ ১৫ · ৫৮-এর মধ্যে · মডিউল ৪
Home / Courses / Full-Stack Web Frameworks / Flux/Redux প্যাটার্ন

Flux/Redux প্যাটার্ন

The Flux/Redux pattern
৯ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ইউনিডাইরেকশনাল ডেটা-ফ্লো কী এবং কেন এটি স্টেট ট্রেস করা সহজ করে তোলে
  • Action, Dispatcher/Store, ও View — এই তিনটি অংশের ভূমিকা
  • একটি SVG ডায়াগ্রামে সম্পূর্ণ চক্রটি — Action থেকে Store, Store থেকে View, এবং View থেকে (ব্যবহারকারীর ইন্টারঅ্যাকশনের মাধ্যমে) আবার নতুন Action
  • Python দিয়ে একটি সত্যিকারের, ছোট্ট single-action dispatch — L16-এর পূর্ণাঙ্গ Store-এর প্রথম ধাপ

১ · ইউনিডাইরেকশনাল ডেটা-ফ্লো কী

FluxFluxFacebook-এর তৈরি একটি আর্কিটেকচার প্যাটার্ন যেখানে অ্যাপ্লিকেশন ডেটা সবসময় একই দিকে প্রবাহিত হয় — Action → Store → View। হলো একটি আর্কিটেকচার প্যাটার্ন — এবং Redux তার সবচেয়ে জনপ্রিয়, সরলীকৃত বাস্তবায়ন — যেখানে অ্যাপ্লিকেশনের ডেটা শুধু একটি নির্দিষ্ট দিকে প্রবাহিত হয়। L14-এ আমরা দেখেছি গ্লোবাল স্টেট প্রপ-ড্রিলিং এড়ায়, কিন্তু গ্লোবাল স্টেট নিজেও একটি নতুন সমস্যা তৈরি করতে পারে — যদি যেকোনো কম্পোনেন্ট থেকে সরাসরি সেই স্টেট পরিবর্তন করা যায়, তাহলে বড় অ্যাপে কোন পরিবর্তন কোথা থেকে এলো তা ট্রেস করা কঠিন হয়ে যায়। Flux/Redux এই সমস্যা সমাধান করে একটি কঠোর নিয়ম দিয়ে — স্টেট পরিবর্তনের একমাত্র উপায় হলো একটি Action dispatch করা।

Action {"type": "INCREMENT"} Dispatcher / Store reducer(state, action) -> নতুন state View নতুন state অনুযায়ী রি-রেন্ডার করে dispatch(action) notify (নতুন state) ব্যবহারকারীর ইভেন্ট
Action সবসময় Store-এ dispatch হয়, Store নতুন state হিসেব করে View-কে জানায়, এবং View-তে ব্যবহারকারীর ইন্টারঅ্যাকশন নতুন Action তৈরি করে চক্রটি আবার শুরু করে — কখনো সরাসরি View থেকে Store-এর state পরিবর্তন হয় না।

২ · তিনটি অংশ — Action, Dispatcher/Store, View

Action
"কী ঘটেছে" তা বর্ণনা করা একটি সাধারণ ডেটা অবজেক্ট — সাধারণত একটি type ফিল্ড ও প্রয়োজনীয় অতিরিক্ত ডেটা থাকে। Action নিজে কোনো লজিক ধারণ করে না।
Dispatcher / Store
Action গ্রহণ করে, একটি reducer ফাংশন কল করে নতুন state হিসেব করে, এবং কে কে এই state-এর ওপর নির্ভরশীল তাদের জানায় (notify)।
View
Store-এর বর্তমান state অনুযায়ী রেন্ডার করে; ব্যবহারকারীর ইন্টারঅ্যাকশন সরাসরি state পরিবর্তন করে না — বরং একটি নতুন Action dispatch করে।

৩ · একটি ছোট্ট, সত্যিকারের single-action dispatch

নিচের কোড সেলে এখনো L16-এর পূর্ণাঙ্গ Store ক্লাস তৈরি করা হয়নি — বরং সবচেয়ে ছোট আকারে ইউনিডাইরেকশনাল চক্রটি দেখানো হয়েছে: একটি reducer ফাংশন, একটি dispatch() ফাংশন যা reducer কল করে state আপডেট করে, আর একটি view() ফাংশন যা বর্তমান state অনুযায়ী আউটপুট তৈরি করে।

Python
# একটি অ্যাকশন, একটি সাধারণ dispatch -- ইউনিডাইরেকশনাল ফ্লো'র সবচেয়ে ছোট রূপ
# (L16-এ এটি একটি পূর্ণাঙ্গ Store ক্লাস + subscribe()-এ সম্প্রসারিত হবে)

state = {"count": 0}

def reducer(state, action):
    if action["type"] == "INCREMENT":
        return {**state, "count": state["count"] + 1}
    elif action["type"] == "DECREMENT":
        return {**state, "count": state["count"] - 1}
    return state

def dispatch(state, action):
    new_state = reducer(state, action)
    print(f"অ্যাকশন dispatch হলো: {action}")
    print(f"  পুরনো state: {state}  ->  নতুন state: {new_state}")
    return new_state

def view(state):
    return f"[View] বর্তমান কাউন্ট দেখানো হচ্ছে: {state['count']}"

print(view(state))
print("-" * 50)

state = dispatch(state, {"type": "INCREMENT"})
print(view(state))
print("-" * 50)

state = dispatch(state, {"type": "INCREMENT"})
print(view(state))
print("-" * 50)

state = dispatch(state, {"type": "DECREMENT"})
print(view(state))

    
লক্ষ্য করুন view() ফাংশন কখনো সরাসরি state["count"] পরিবর্তন করে না — এটি শুধু state পড়ে। state পরিবর্তনের একমাত্র পথ হলো dispatch()-কে একটি Action পাঠানো, যা reducer()-কে কল করে। এই নিয়মটিই Flux/Redux-কে "predictable" (অনুমানযোগ্য) করে তোলে — যেকোনো state পরিবর্তন ট্রেস করতে হলে শুধু dispatch হওয়া Action-গুলোর তালিকা দেখলেই চলে।
মূল কথা · Key takeaway

Flux/Redux প্যাটার্নে ডেটা সবসময় Action → Store → View — এই একমুখী পথে প্রবাহিত হয়। View কখনো state সরাসরি পরিবর্তন করে না, শুধু নতুন Action dispatch করে। এই কঠোর নিয়মই বড়, জটিল অ্যাপ্লিকেশনে স্টেট ট্রেস করা ও ডিবাগ করা সহজ করে তোলে। L16-এ আমরা এই ধারণাকে একটি পূর্ণাঙ্গ Store ক্লাসে (একাধিক অ্যাকশন টাইপ, subscribe() সহ) সম্প্রসারিত করব।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ View কেন সরাসরি state পরিবর্তন করতে পারে না — Action dispatch করতেই কেন হয়?

যদি যেকোনো View সরাসরি state পরিবর্তন করতে পারত, তাহলে বড় অ্যাপে একটি bug খুঁজতে গিয়ে পুরো কোডবেস খুঁজে দেখতে হতো কোন কোন জায়গা থেকে state পরিবর্তন হতে পারে। Action dispatch-কে বাধ্যতামূলক করলে সব state পরিবর্তনের একটি একক, ট্রেসযোগ্য প্রবেশপথ (entry point) তৈরি হয় — reducer। ফলে ডিবাগ করার সময় শুধু "কোন কোন Action dispatch হয়েছিল" জানলেই state-এর ইতিহাস পুনর্গঠন করা যায়।

প্র ০২ reducer ফাংশন কেন সবসময় একটি নতুন state অবজেক্ট রিটার্ন করে, পুরনোটা mutate করে না?

পুরনো ও নতুন state আলাদা অবজেক্ট থাকলে সহজেই id(old) != id(new) চেক করে বোঝা যায় state আসলেই পরিবর্তিত হয়েছে কি না — এটি View-কে দক্ষতার সাথে সিদ্ধান্ত নিতে সাহায্য করে কখন রি-রেন্ডার করা দরকার। এছাড়া immutability থাকলে "state এর পুরনো সংস্করণগুলো" (যেমন undo/redo বা time-travel debugging-এ) নিরাপদে সংরক্ষণ করা যায়, কারণ সেগুলো ভবিষ্যতে ভুলবশত পরিবর্তিত হয়ে যাওয়ার ঝুঁকি থাকে না। L16-এ এই immutability সরাসরি id() দিয়ে যাচাই করা হবে।

প্র ০৩ উপরের কোডে তিনটি dispatch-এর পর state["count"]-এর চূড়ান্ত মান কত হবে?

শুরুতে count ০। প্রথম INCREMENT এটিকে ১ করে, দ্বিতীয় INCREMENT এটিকে ২ করে, এবং DECREMENT এটিকে আবার ১-এ নামিয়ে আনে। তাই শেষ view(state) কল "বর্তমান কাউন্ট দেখানো হচ্ছে: 1" প্রিন্ট করবে।

অনুশীলন

  1. চিন্তা করুন: "ইউনিডাইরেকশনাল" (একমুখী) ডেটা-ফ্লো-র বিপরীত হলো "বাইডাইরেকশনাল" (দ্বিমুখী) ডেটা-ফ্লো, যেখানে View সরাসরি state পরিবর্তন করতে পারে এবং state-ও সরাসরি View-কে জানায়। এই বাইডাইরেকশনাল পদ্ধতির সম্ভাব্য সুবিধা কী হতে পারে?

    ছোট, সাধারণ অ্যাপ্লিকেশনে বাইডাইরেকশনাল বাইন্ডিং কম কোড লিখে দ্রুত ফলাফল দিতে পারে — যেমন একটি ইনপুট ফিল্ড সরাসরি একটি ভ্যারিয়েবলের সাথে "বাইন্ড" করা, আলাদা করে Action/reducer লেখার প্রয়োজন হয় না। তবে অ্যাপ বড় হলে এই সুবিধাই সমস্যা হয়ে যায় — অনেক জায়গা থেকে state পরিবর্তন হতে পারে বলে ট্রেস করা কঠিন হয়ে পড়ে, যা Flux/Redux ঠিক এড়াতে চায়।

  2. পরীক্ষা করুন: উপরের কোড সেলে reducer ফাংশনে একটি নতুন অ্যাকশন টাইপ "RESET" যোগ করুন যা count-কে ০-তে ফিরিয়ে দেয়, তারপর শেষে state = dispatch(state, {"type": "RESET"}) ও print(view(state)) যোগ করে Run চাপুন।

    reducer-এ elif action["type"] == "RESET": return {**state, "count": 0} যোগ করলে, শেষ dispatch-এর পর view(state) "বর্তমান কাউন্ট দেখানো হচ্ছে: 0" প্রিন্ট করবে — আগের ইতিহাস (INCREMENT/DECREMENT) যাই হোক না কেন, কারণ reducer প্রতিটি Action-কে বর্তমান state থেকে স্বাধীনভাবে নতুন state হিসেব করে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
লোকাল বনাম গ্লোবাল স্টেট