পাঠ ১১ · ৫৮-এর মধ্যে · মডিউল ৩
Home / Courses / Full-Stack Web Frameworks / Props, State ও লাইফসাইকেল

Props, State ও কম্পোনেন্ট লাইফসাইকেল

Props, state, and the component lifecycle
১০ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • Props ও state-এর মধ্যে পার্থক্য — মালিকানা ও পরিবর্তনযোগ্যতার দিক থেকে
  • কম্পোনেন্ট লাইফসাইকেলের তিনটি প্রধান পর্যায় — mount, update, unmount
  • set_state() কীভাবে রিরেন্ডার ট্রিগার করে
  • একটি সম্পূর্ণ লাইফসাইকেল ধারাক্রম বাস্তবে ট্রেস করে দেখা

১ · Props বনাম State

L09-এ আমরা দেখেছি props কীভাবে প্যারেন্ট থেকে চাইল্ড কম্পোনেন্টে ডেটা পাঠায়। কিন্তু একটি কম্পোনেন্টের নিজস্ব, অভ্যন্তরীণ ডেটাও থাকতে পারে — যেমন একটি কাউন্টার কম্পোনেন্ট নিজেই জানে বর্তমান কাউন্ট কত। এই ধরনের কম্পোনেন্টের-নিজস্ব-মালিকানার ডেটাকে বলা হয় stateStateকম্পোনেন্টের নিজস্ব, পরিবর্তনযোগ্য ডেটা — এটি বদলালে কম্পোনেন্ট স্বয়ংক্রিয়ভাবে আবার রেন্ডার হয়।।

Props
প্যারেন্ট থেকে পাঠানো হয় · কম্পোনেন্টের কাছে অপরিবর্তনীয় (read-only) · প্যারেন্ট নতুন props পাঠালে বদলায় (L10-এর update_props প্যাচের মতো)।
State
কম্পোনেন্ট নিজেই তৈরি ও মালিক · কম্পোনেন্টের নিজস্ব মেথড দিয়ে পরিবর্তনযোগ্য · বদলালে স্বয়ংক্রিয়ভাবে রিরেন্ডার ট্রিগার করে।

একটি সহজ নিয়ম: props প্রশ্ন করে "আমাকে কী দেওয়া হয়েছে?", আর state প্রশ্ন করে "আমি এখন কী মনে রেখেছি?"। একটি UserCard কম্পোনেন্টের name props হতে পারে (প্যারেন্ট ঠিক করে দেয় কার নাম দেখাতে হবে), কিন্তু "এই কার্ডটি এক্সপ্যান্ড করা আছে কি না" — এটা প্রায়ই সেই কার্ডের নিজস্ব state হয় (ব্যবহারকারী ক্লিক করলে সেই কম্পোনেন্টই নিজে সিদ্ধান্ত নেয়)।

২ · কম্পোনেন্ট লাইফসাইকেল — mount, update, unmount

প্রতিটি কম্পোনেন্ট তার "জীবনে" কয়েকটি নির্দিষ্ট পর্যায় দিয়ে যায় — একে বলা হয় লাইফসাইকেল:

  • mount: কম্পোনেন্ট প্রথমবার তৈরি হয়ে DOM-এ বসে (রেন্ডার হয়ে স্ক্রিনে দেখা যায়)। এখানেই প্রাথমিক state সেট করা বা ডেটা fetch করার মতো কাজ হয়।
  • update: কম্পোনেন্ট ইতিমধ্যে DOM-এ আছে, কিন্তু তার props বদলেছে (প্যারেন্ট থেকে নতুন ডেটা এসেছে) — তাই এটি আবার রেন্ডার হয়।
  • unmount: কম্পোনেন্ট DOM থেকে সরিয়ে ফেলা হচ্ছে (যেমন ব্যবহারকারী অন্য পেজে গেল) — এখানে টাইমার/লিসেনার পরিষ্কার করার মতো কাজ হয়।

এর সাথে set_state() একটি বিশেষ ঘটনা — এটি update-এরই একটি রূপ, কিন্তু ট্রিগার হয় প্যারেন্ট থেকে নয়, কম্পোনেন্টের নিজের ভেতর থেকে।

৩ · Python কোডে — একটি কম্পোনেন্ট instance-এর সম্পূর্ণ লাইফসাইকেল

নিচের কোড সেলে একটি Component ক্লাস লেখা হয়েছে যা একটি সত্যিকারের কম্পোনেন্ট "instance" সিমুলেট করে — এর mount(), update(new_props), set_state(updates), unmount() মেথড সত্যিই কল হয়, রিরেন্ডার লজিক সত্যিই চলে, এবং প্রতিটি ধাপ প্রিন্ট হয় — একটি Counter কম্পোনেন্টের জন্ম থেকে মৃত্যু পর্যন্ত সম্পূর্ণ ধারাক্রম ট্রেস করে।

Python
class Component:
    """একটি কম্পোনেন্ট instance -- props (বাইরে থেকে) ও state (নিজস্ব) দুটোই রাখে"""

    def __init__(self, name, props):
        self.name = name
        self.props = dict(props)   # প্যারেন্ট থেকে পাওয়া, শুরুতে
        self.state = {}            # কম্পোনেন্টের নিজস্ব -- এখনো খালি
        self.is_mounted = False
        self.render_count = 0

    def render(self):
        """props ও state-এর বর্তমান মান অনুযায়ী আউটপুট 'তৈরি' করে"""
        self.render_count += 1
        print(f"  [render #{self.render_count}] {self.name} -> props={self.props}, state={self.state}")

    def mount(self):
        """লাইফসাইকেল ধাপ ১: প্রথমবার তৈরি হয়ে 'DOM'-এ বসে"""
        self.is_mounted = True
        print(f"[mount] {self.name} মাউন্ট হলো (props={self.props})")
        self.render()

    def update(self, new_props):
        """লাইফসাইকেল ধাপ ২: প্যারেন্ট থেকে নতুন props এলে আপডেট হয়"""
        if not self.is_mounted:
            print(f"[update] {self.name} এখনো মাউন্ট হয়নি -- আপডেট বাতিল")
            return
        old_props = self.props
        self.props = dict(new_props)
        print(f"[update] {self.name} props বদলেছে: {old_props} -> {self.props}")
        self.render()

    def set_state(self, updates):
        """কম্পোনেন্টের নিজস্ব state বদলায় -- এবং স্বয়ংক্রিয়ভাবে রিরেন্ডার ট্রিগার করে"""
        if not self.is_mounted:
            print(f"[set_state] {self.name} এখনো মাউন্ট হয়নি -- set_state বাতিল")
            return
        old_state = self.state
        self.state = {**self.state, **updates}
        print(f"[set_state] {self.name} state বদলেছে: {old_state} -> {self.state}")
        self.render()  # <-- এটাই key: state বদলালে স্বয়ংক্রিয় রিরেন্ডার

    def unmount(self):
        """লাইফসাইকেল ধাপ ৩: 'DOM' থেকে সরিয়ে ফেলা হচ্ছে"""
        self.is_mounted = False
        print(f"[unmount] {self.name} আনমাউন্ট হলো (মোট render হয়েছিল {self.render_count} বার)")


# --- সম্পূর্ণ লাইফসাইকেল ধারাক্রম ট্রেস করা ---
counter = Component("Counter", props={"initial_value": 0, "step": 1})

counter.mount()                          # ধাপ ১: মাউন্ট -- প্রথম render
counter.set_state({"count": 0})          # নিজস্ব state শুরু করা -- render #2
counter.set_state({"count": 1})          # ব্যবহারকারী ক্লিক করল -- render #3
counter.set_state({"count": 2})          # আবার ক্লিক -- render #4
counter.update({"initial_value": 0, "step": 5})  # প্যারেন্ট নতুন props পাঠাল -- render #5
counter.unmount()                        # ধাপ ৩: আনমাউন্ট, আর কোনো render নয়

print(f"\nচূড়ান্ত অবস্থা যাচাই -- is_mounted={counter.is_mounted}, মোট render={counter.render_count}, শেষ state={counter.state}")

    
লক্ষ্য করুন set_state() ও update() দুটোই শেষে self.render() কল করে — কিন্তু ট্রিগার হওয়ার উৎস আলাদা। update() ট্রিগার হয় বাইরে থেকে (প্যারেন্ট নতুন props পাঠালে), আর set_state() ট্রিগার হয় ভেতর থেকে (কম্পোনেন্ট নিজেই তার state বদলানোর সিদ্ধান্ত নেয়, যেমন একটি ক্লিক হ্যান্ডলারের ভেতরে)। কোডে mount()-এর আগে set_state() বা update() কল করলে কিছুই render হয় না, ঠিক যেমন বাস্তব ফ্রেমওয়ার্কে একটি আনমাউন্টেড কম্পোনেন্টে state বদলানো একটি চেনা বাগ।
মূল কথা · Key takeaway

Props হলো ইনপুট — প্যারেন্ট থেকে আসে, কম্পোনেন্ট নিজে বদলায় না। State হলো কম্পোনেন্টের নিজস্ব স্মৃতি — এটি কম্পোনেন্ট নিজেই বদলায়, আর বদলালেই স্বয়ংক্রিয়ভাবে রিরেন্ডার হয়। এই রেন্ডার-চক্রকে ঘিরেই একটি কম্পোনেন্টের সম্পূর্ণ লাইফসাইকেল গঠিত — mount (জন্ম), update/set_state (পুনর্জীবন), unmount (মৃত্যু)।

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

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

প্র ০১ কোড সেলে render_count শেষে কত হবে, এবং কেন?

render_count শেষে ৫ হবে। প্রতিটি render() কল ১ করে বাড়ায়, আর কোডে মোট পাঁচটি render-ট্রিগারকারী কল হয়েছে: mount() (১), তিনটি set_state() কল (২, ৩, ৪), আর একটি update() কল (৫)। unmount() কোনো render করে না, তাই সেটা গণনায় যোগ হয় না।

প্র ০২ যদি unmount()-এর পরে আবার counter.set_state({"count": 99}) কল করা হয়, তাহলে কী ঘটবে?

কিছুই render হবে না। unmount() কল করার ফলে self.is_mounted False হয়ে গেছে, আর set_state() মেথডের শুরুতেই if not self.is_mounted: চেক আছে — তাই এটি শুধু একটি সতর্কবার্তা প্রিন্ট করে ফিরে আসবে, state বা render_count কোনোটাই বদলাবে না। এটি বাস্তব ফ্রেমওয়ার্কে একটি সাধারণ সুরক্ষা প্যাটার্ন — আনমাউন্ট হয়ে যাওয়া কম্পোনেন্টে state আপডেট করার চেষ্টা প্রতিরোধ করা।

প্র ০৩ শেষ update({"initial_value": 0, "step": 5}) কলটি state-কে প্রভাবিত করে কি না?

না। update() মেথড শুধু self.props পরিবর্তন করে — এটি কখনো self.state স্পর্শ করে না। কোডটি চালালে দেখা যাবে শেষ state ({"count": 2}) অপরিবর্তিতই থাকে, শুধু props-এর "step" মান ১ থেকে ৫ হয়ে যায়। এটাই props ও state-এর মধ্যে সীমারেখা প্রতিষ্ঠা করে — একটি বদলালে অন্যটি নিজে থেকে বদলায় না।

অনুশীলন

  1. চিন্তা করুন: একটি "থিম টগল বাটন" কম্পোনেন্ট চিন্তা করুন যা লাইট/ডার্ক মোড সুইচ করে — "বর্তমান থিম লাইট না ডার্ক" এটা কি props হওয়া উচিত না state — এবং কেন সেটা প্রজেক্টভেদে পাল্টাতে পারে?

    দুটোই সম্ভব, নির্ভর করে থিমটি কোথায় ব্যবহৃত হবে তার উপর। যদি শুধু ওই বাটনটির নিজের জন্য থিম প্রাসঙ্গিক হয় (অন্য কোনো কম্পোনেন্টের দরকার নেই), এটি সেই বাটনের নিজস্ব state হতে পারে। কিন্তু বাস্তবে থিম প্রায়ই পুরো অ্যাপ জুড়ে অনেক কম্পোনেন্টের দরকার হয় (হেডার, ব্যাকগ্রাউন্ড, টেক্সট রং) — তখন এটি একটি উঁচু-স্তরের কম্পোনেন্টের state হিসেবে রাখা হয় এবং props হিসেবে নিচের দিকে পাঠানো হয় (অথবা L14-১৭-এ দেখা গ্লোবাল স্টেট/context প্যাটার্ন ব্যবহার করা হয়)।

  2. পরীক্ষা করুন: উপরের কোড সেলে counter.unmount()-এর ঠিক আগে আরেকটি লাইন যোগ করুন — counter.set_state({"count": 100}) — এবং Run চেপে দেখুন render_count ৫ থেকে ৬ হয় কি না এবং চূড়ান্ত state-এ count-এর মান কী হয়।

    হ্যাঁ, unmount()-এর আগে কল করা হলে কম্পোনেন্ট তখনও মাউন্টেড অবস্থায় থাকে, তাই set_state({"count": 100}) স্বাভাবিকভাবেই কাজ করবে — এটি আরেকটি render ট্রিগার করবে (render_count হবে ৬), আর self.state["count"] হয়ে যাবে 100। তারপর unmount() কল হলে সেই count=100 অবস্থাতেই কম্পোনেন্ট আনমাউন্ট হবে — চূড়ান্ত প্রিন্টে শেষ state={'count': 100} দেখা যাবে।

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

  • পরের পাঠ L12 React, Vue, Angular কীভাবে আলাদাভাবে রিঅ্যাক্টিভিটি ও রিরেন্ডার হ্যান্ডেল করে তার তুলনা।
  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ আর্কিটেকচার প্যাটার্ন, ফ্রন্ট-এন্ড/ব্যাক-এন্ড ফ্রেমওয়ার্ক ফান্ডামেন্টাল, স্টেট ম্যানেজমেন্ট, REST API, ORM, অথেন্টিকেশন, রেন্ডারিং স্ট্র্যাটেজি ও ডিপ্লয়মেন্ট।
  • JavaScript Programming কোর্স সহোদর কোর্স ক্লাস, মেথড ও অবজেক্ট-ভিত্তিক প্রোগ্রামিং-এর ভাষাগত ভিত্তি সেই কোর্সেই তৈরি হয়েছে।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics ও Full-Stack Web Frameworks — সব এক জায়গায়।
আগের পাঠ
ভার্চুয়াল DOM ও রিকনসিলিয়েশন