مدیریت State در کامپوننت‌های کلاسی ری‌اکت (React Class Components)

در دنیای ری‌اکت (React)، مفهوم State به ساختاری اشاره دارد که تغییرات داده‌ها را در طول زمان و در جریان تعامل کاربر با برنامه دنبال می‌کند. مدیریت صحیح State یکی از کلیدی‌ترین مهارت‌ها برای ساخت وب‌اپلیکیشن‌های پویا و تعاملی است؛ از ذخیره ورودی‌های یک فرم گرفته تا مدیریت داده‌های دریافتی از API.

نکته: اگرچه مستندات رسمی ری‌اکت استفاده از React Hooks و کامپوننت‌های تابعی (Functional Components) را برای کدنویسی جدید پیشنهاد می‌کنند، اما درک نحوه مدیریت State در کامپوننت‌های کلاسی همچنان اهمیت بالایی دارد. این دانش به شما کمک می‌کند کدهای قدیمی‌تر (Legacy Code) را عیب‌یابی و پشتیبانی کنید. همچنین برخی متدهای متداول مانند componentDidCatch (برای مدیریت خطاها) تنها در کامپوننت‌های کلاسی در دسترس هستند.

در این مقاله آموزش می‌دهیم که چگونه State را با مقادیر ثابت (Static) و متغیر (بر اساس State قبلی) مقداردهی و به‌روزرسانی کنید. برای درک بهتر، یک صفحه فروشگاه ساده همراه با سبد خرید خواهیم ساخت.

پیش‌نیازها

برای اجرای این آموزش به موارد زیر نیاز دارید:

  • Node.js و npm: در این آموزش از نسخه مدرن Node.js (نسخه LTS مانند v18 یا v20) و npm استفاده شده است.

  • آشنایی اولیه با JavaScript (ES6)، HTML و CSS.

گام ۱ — راه‌اندازی پروژه

ابتدا یک پروژه جدید ری‌اکت ایجاد می‌کنیم و کدهای اضافی پیش‌فرض را پاکسازی می‌کنیم.

دستور زیر را در ترمینال اجرا کنید:

Bash

npx create-react-app state-class-tutorial
cd state-class-tutorial

تنظیم registry پکیج منیجر npm (ویژه زیرساخت پارمین کلود)

برای افزایش سرعت دانلود پکیج‌ها و جلوگیری از مشکلات تحریم، می‌توانید از میرور رسمی پارمین کلود برای npm استفاده کنید:

Bash

npm config set registry https://npm.parmincloud.ir

سپس پروژه را اجرا کنید:

Bash

npm start

برنامه روی آدرس http://localhost:3000 در مرورگر باز می‌شود.

پاکسازی کدهای پیش‌فرض

۱. فایل src/App.js را باز کنید و محتوای آن را با کد زیر جایگزین کنید:

JavaScript

import React from 'react';
import './App.css';

function App() {
  return <></>;
}

export default App;

۲. فایل‌های اضافی را حذف کنید:

Bash

rm src/logo.svg

۳. یک ساختار پوشه‌ای منظم ایجاد کنید:

Bash

mkdir -p src/components/App
mv src/App.* src/components/App/

۴. مسیر import را در فایل src/index.js به‌روزرسانی کنید:

JavaScript

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './components/App/App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

گام ۲ — تعریف و استفاده از State در کامپوننت

حالا کامپوننت محصول (Product) را ایجاد می‌کنیم تا ساختار اولیه سبد خرید را تشکیل دهیم.

پوشه و فایل کامپوننت را بسازید:

Bash

mkdir -p src/components/Product

فایل src/components/Product/Product.js را ایجاد کرده و کدهای زیر را در آن قرار دهید:

JavaScript

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {
  // تعریف State اولیه
  state = {
    cart: [],
    total: 0
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  // متدی برای فرمت‌دهی قیمت
  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions);
  }

  render() {
    return (
      <div className="wrapper">
        <div>
          سبد خرید: {this.state.cart.length} آیتم
        </div>
        <div>مجموع قیمت: {this.getTotal()} هزار تومان</div>

        <div className="product">
          <span role="img" aria-label="ice cream">🍦</span>
        </div>
        <button>افزودن</button>
        <button>حذف</button>
      </div>
    );
  }
}

استایل‌های اولیه را در src/components/Product/Product.css اضافه کنید:

CSS

.product span {
    font-size: 100px;
}

.wrapper {
    padding: 20px;
    font-size: 20px;
    direction: rtl;
    text-align: right;
}

.wrapper button {
    font-size: 18px;
    margin: 5px;
    padding: 5px 15px;
    background: none;
    border: 1px solid #ccc;
    cursor: pointer;
}

سپس کامپوننت Product را در src/components/App/App.js فراخوانی کنید:

JavaScript

import React from 'react';
import Product from '../Product/Product';

function App() {
  return <Product />;
}

export default App;

گام ۳ — به‌روزرسانی State با مقادیر ثابت (Static)

برای تغییر دادن State در ری‌اکت، هرگز نباید this.state را به مستقیم تغییر دهید. در عوض، باید از متد ویژه this.setState() استفاده کنید.

هنگامی که مقدار جدید متغیر وابسته به مقدار قبلی آن نیست (مثلاً دریافت یک داده جدید از API که جایگزین داده قبلی می‌شود)، می‌توانید یک Object مستقیم به setState پاس دهید.

فایل src/components/Product/Product.js را به‌روزرسانی کنید:

JavaScript

import React, { Component } from 'react';
import './Product.css';

export default class Product extends Component {
  state = {
    cart: [],
    total: 0
  }

  // متد افزودن آیتم با مقدار ثابت
  add = () => {
    this.setState({
      cart: ['بستنی'],
      total: 50
    });
  }

  // متد خالی کردن سبد خرید
  remove = () => {
    this.setState({
      cart: []
    });
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  getTotal = () => {
    return this.state.total.toLocaleString(undefined, this.currencyOptions);
  }

  render() {
    return (
      <div className="wrapper">
        <div>
          سبد خرید: {this.state.cart.length} آیتم
        </div>
        <div>مجموع قیمت: {this.getTotal()} هزار تومان</div>

        <div className="product">
          <span role="img" aria-label="ice cream">🍦</span>
        </div>
        <button onClick={this.add}>افزودن</button>
        <button onClick={this.remove}>حذف</button>
      </div>
    );
  }
}

نکته مهم درباره Arrow Functions: تعریف متدهای add و remove به صورت Arrow Function (() => {}) باعث می‌شود کلیدواژه this به بافت (Context) خودِ کلاس اشاره کند و هنگام اجرای this.setState با خطا مواجه نشوید.

گام ۴ — به‌روزرسانی State بر اساس State قبلی

در بسیاری از سناریوها، مقدار جدید State به مقدار قبلی آن وابسته است (مثلاً اضافه کردن یک آیتم جدید به آرایه سبد خرید یا افزایش قیمت کل).

از آنجا که ری‌اکت جهت بهینه‌سازی کارایی، فراخوانی‌های setState را دسته‌بندی (Batch) می‌کند، ارجاع مستقیم به this.state درون setState ممکن است باعث ایجاد باگ و خواندن مقادیر قدیمی شود. راه حل استاندارد، پاس دادن یک تابع به setState است.

لیست محصولات را به کامپوننت اضافه کرده و متدهای add و remove را بازنویسی می‌کنیم:

JavaScript

import React, { Component } from 'react';
import './Product.css';

const products = [
  { emoji: '🍦', name: 'بستنی', price: 50 },
  { emoji: '🍩', name: 'دونات', price: 25 },
  { emoji: '🍉', name: 'هندوانه', price: 40 }
];

export default class Product extends Component {
  state = {
    cart: [],
  }

  // افزودن محصول به سبد با حفظ محصولات قبلی
  add = (product) => {
    this.setState(prevState => ({
      cart: [...prevState.cart, product],
    }));
  }

  // حذف یک واحد از محصول انتخاب شده
  remove = (product) => {
    this.setState(prevState => {
      const cart = [...prevState.cart];
      const productIndex = cart.findIndex(p => p.name === product.name);
      
      if (productIndex < 0) {
        return; // اگر محصول در سبد نبود، رندر مجدد انجام نمی‌شود
      }
      
      cart.splice(productIndex, 1);
      return { cart };
    });
  }

  currencyOptions = {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }

  // محاسبه مجموع قیمت بر اساس محتویات سبد
  getTotal = () => {
    const total = this.state.cart.reduce((totalCost, item) => totalCost + item.price, 0);
    return total.toLocaleString(undefined, this.currencyOptions);
  }

  render() {
    return (
      <div className="wrapper">
        <div>
          سبد خرید: {this.state.cart.length} آیتم
        </div>
        <div>مجموع قیمت: {this.getTotal()} هزار تومان</div>

        <div>
          {products.map(product => (
            <div key={product.name}>
              <div className="product">
                <span role="img" aria-label={product.name}>{product.emoji}</span>
              </div>
              <button onClick={() => this.add(product)}>افزودن</button>
              <button onClick={() => this.remove(product)}>حذف</button>
            </div>
          ))}
        </div>
      </div>
    );
  }
}

نکات کلیدی در این روش:

۱. عدم تغییر مستقیم (Immutability): برای تغییر آرایه cart ابتدا با Spread Operator ([...prevState.cart]) یک کپی از آن تهیه کردیم. ۲. جلوگیری از رندر اضافی: در متد remove اگر محصول در سبد وجود نداشته باشد، مقدار undefined برگردانده می‌شود تا ری‌اکت کامپوننت را بی‌دلیل مجدداً رندر نکند.

جمع‌بندی

در این مقاله با نحوه مدیریت State در کامپوننت‌های کلاسی ری‌اکت آشنا شدید. آموختید که چطور State اولیه را مقداردهی کنید، مقادیر آن را با استفاده از متد setState به صورت استاتیک یا بر اساس State قبلی تغییر دهید و از تغییر مستقیم داده‌ها (Mutation) جلوگیری کنید.

از همراهی شما با پارمین کلود سپاسگزاریم.

Click to rate this post!
[Total: 0 Average: 0]

نظرات کاربران

دیدگاهی بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *