راهنمای مبتدی استفاده از React با Redux

با توجه به فریم‌ورک‌ها و کتابخانه‌های جدیدی که هر هفته در دنیای جاوااسکریپت منتشر می‌شود، ممکن نیست بدون اینکه بدانید آیا واقعا به آن نیاز دارید به بخش "شروع" مستندات آن مراجعه کنید.

راهنمای مبتدی استفاده از React با Redux

Redux حدودا از سال 2015 بوده است و دلایلی برای محبوبیت آن وجود دارد. اگر شما هنوز در مورد آن چیزی نشنیده‌اید یا نمی‌دانید چرا باید از آن در پروژه React خود استفاده کنید، هم‌اکنون در جای مناسبی قرار گرفته‌اید و این مقاله برای شماست.

ما قصد داریم یک برنامه ساده بسازیم تا شما بتوانید استفاده از React در کنار Redux را در عمل ببینید.

ما فرض می‌کنیم که شما دانش پایه‌ای از React دارید، اگر ندارید به آموزش React مراجعه کنید.

Redux چیست؟

Redux یک نگهدارنده (container) قابل پیش‌بینی برای برنامه‌های جاوااسکریپت است. مفهوم اصلی پشت آن این است که تمام وضعیت (state) برنامه را در یک مکان متمرکز ذخیره می‌کند. اما Redux چطور state قابل پیش‌بینی را ایجاد می‌کند؟ به منظور تغییر state، Redux شما را مجبور می‌کند تا اقدام (action) را بفرستید، که اشیای ساده جاوااسکریپت هستند. این بدان معناست که اگر state تغییر کند، یک action باید فرستاده شده باشد.

چیزی که stateها و actionها را در کنار هم قرار می‌دهد توابعی به نام reducers هستند. باز هم این‌ها توابع ساده جاوااسکریپت هستند. state و action برنامه به عنوان پارامترها گرفته می‌شوند و state بعدی بازگردانده می‌شود. مساله مهمی که در مورد reducerها وجود دارد این است که آن‌ها pure function هستند. آن‌ها شیء state را تغییر نمی‌دهند، اشکال‌زدایی را در Redux DevTools ایجاد می‌کنند.

لازم به ذکر است که Redux یک کتابخانه مستقل و بسیار انعطاف‌پذیر است. شما می‌توانید آن را با React، Angular، jQuery، یا حتی vanilla JavaScript استفاده کنید. Redux به خوبی با React کار می‌کند، زیرا React به شما اجازه می‌دهد تا UI را به عنوان تابعی از state تعریف کنید و مدیریت state چیزی است که Redux به خوبی انجام می‌دهد.

نیاز به ابزار مدیریت وضعیت (state management)

در نگاه اول، ممکن است به نظر برسد که React تمام مدیریت state را که همواره به آن نیاز دارید ارائه می‌دهد. هر کامپوننت می‌تواند stateای باشد و شما به راحتی می‌توانید هر داده‌ای را از طریق    props  ارسال کنید. افزودن Redux به پروژه خود بدون تجربه مشکلی با جریان داده React ابتدا می‌تواند کمی گیج‌کننده باشد. بیایید به نمودار زیر نگاهی بیندازیم:

این نمونه‌ای از جریان داده یک‌ طرفه React است. در این مثال، کامپوننت والد کامپوننت فرزندان را با یک عکس فوری از state آن از طریق ویژگی‌های read-only به نام " props" ارائه می‌دهد. " Component A" و " Component B" داده‌های مشابهی را به اشتراک می‌گذارند، که از " Parent Component" ارسال شده‌اند. مثال بعدی نشان می‌دهد کجا مشکلات شروع می‌شوند.

این چیزی است که اغلب در پروژه‌های React اتفاق می‌افتد و حتی لزوما بزرگ نیست. اگر " Component C" و " Component D" نیاز به داده‌های مشابه داشته باشند، شما باید داده‌ها را از نزدیک‌ترین والد مشترک ارسال کنید، که " Parent Component" است. داده‌ها از چندین سطح از کامپوننت‌های واسط عبور می‌کنند، که ممکن است حتی نیاز به آن داده‌ها نداشته باشند.

ارسال داده‌ها به این روش می‌تواند منجر به مشکلاتی شود. هر کامپوننت در این ساختار به صورت محکم وصل شده است، و شما را در حرکت آزادانه کامپوننت ناتوان می‌سازد. همچنین می‌تواند عملکرد برنامه شما را تحت تاثیر قرار دهد، زیرا هر به‌روزرسانی state تمام کامپوننت‌های فرزندان را مجددا رندر می‌کند. اگر در موقعیتی قرار بگیرید که دو کامپوننت نیاز به داده مشابه‌ای داشته باشند، اغلب شما باید کد چندین کامپوننت را برای تنظیم جریان داده‌ها از بالا به پایین درخت تغییر دهید. این امر اغلب زمان‌بر و زجرآور است.

این جایی است که Redux وارد عمل می‌شود که مشکلات بیان شده در بالا را با ارائه مخزن داده متمرکز حل می‌کند که چیزی بیش از شیء جاوااسکریپت با چند متد بر روی آن نیست. Redux به شما این امکان را می‌دهد تا داده‌ها را در یک مکان نگه دارید، و در هر جا در برنامه‌یتان به آن‌ها دسترسی داشته باشید. بیایید نگاهی به نحوه نمایش آن در سناریوی برنامه React بیاندازیم:

همان‌طور که می‌بینید، با Redux شما دیگر مجبور نیستید داده‌ها را از طریق چندین سطح از کامپوننت‌های تو در تو ارسال کنید. می‌تواند از هر جایی در برنامه قابل دسترس باشد. کامپوننت‌هایی که نیاز به داده دارند، می‌توانند به طور مستقیم از مخزن به آن‌ دسترسی داشته باشند. کامپوننت‌های کمتری با جریان داده‌ها درگیر هستند و کد ساده تر می‌شود.

چگونه یک برنامه با استفاده از React با Redux ایجاد کنیم

برای درک نحوه استفاده از React و Redux در کنار هم، یک برنامه نمونه ایجاد خواهیم کرد. این برنامه یک شمارنده ساده است که state جاری آن را نشان داده و به کاربر اجازه می‌دهد مقدار آن را افزایش و کاهش دهد. اگر نمی‌خواهید همه مراحل را خودتان انجام دهید، می‌توانید این ریپازیتوری را clone کنید.

مرحله 1

برای این مثال ساده از create-react-app استفاده می‌کنیم. اگر از قبل آن را نصب دارید می‌توانید از این مرحله بگذرید:

npm install -g create-react-app

مرحله 2

یک برنامه ایجاد کنید:

create-react-app react-redux-counter

کمی زمان می‌برد. بعد از انجام این کار، پوشه پروژه خود را وارد کنید:

cd react-redux-counter

مرحله 3

ما نیاز به Redux (بدیهی است) و پکیج react-redux داریم، که بایندینگ رسمی React برای Redux است. بیایید هر دو را نصب کنیم:

npm install redux react-redux --save

مرحله 4

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

mkdir -p src/store && touch src/store/action-types.js && touch src/store/action-creators.js && touch src/store/reducers.js
mkdir -p src/components && touch src/components/Counter.js && touch src/components/counter.css

مرحله 5

در src/store/action-types.js، بیایید موارد احتمالی actionهایی که می‌توانند در برنامه‌یمان رخ دهند را شرح دهیم:

export const TYPE_INCREMENT = 'INCREMENT'
export const TYPE_DECREMENT = 'DECREMENT'

این خیلی بدیهی است. ما می‌توانیم شمارنده خود را افزایش یا کاهش دهیم.

مرحله 6

در src/store/action-creators.js، سازندگان actionها را تعریف خواهیم کرد. همان‌طور که ممکن است به یاد داشته باشید، actionها اشیای ساده جاوااسکریپت هستند که آنچه را که در برنامه رخ می‌دهد را توصیف می‌کنند.

import { TYPE_INCREMENT, TYPE_DECREMENT } from './action-types'

export const increment = () => ({
  type: TYPE_INCREMENT,
})

export const decrement = () => ({
  type: TYPE_DECREMENT,
})

همان‌طور که می‌بینید، این‌ها توابع معینی هستند که اشیاء (action) را با ویژگی type بازمی‌گردانند. ما کارهای مختلفی را بر اساس action typeها در reducers انجام می‌دهیم. در مثال‌های پیچیده‌تر، actionهای داده‌های اضافی را حمل می‌کنند، اما فقط ویژگی type لازم است و برای مثال ما کافی است.

مرحله 7

در src/store/reducers.js، ما state اولیه برنامه و reducerمان را تعریف می‌کنیم.

import { TYPE_INCREMENT, TYPE_DECREMENT } from './action-types'

const initialState = {
  counter: 0,
}

export const counterReducer =  (state = initialState, action) => {
  switch (action.type) {
    case TYPE_INCREMENT:
      return {
        ...state,
        counter: state.counter + 1,
      }
    case TYPE_DECREMENT:
      return {
        ...state,
        counter: state.counter - 1,
      }
    default:
      return state
  }
}

در یک برنامه Redux، هر بار که actionای ارسال می‌شود، redux هر reducer را فراخوانی خواهد کرد، state فعلی به عنوان اولین پارامتر و action ارسال شده به عنوان پارامتر دوم ارسال می‌شود. برنامه ساده ما یک reducer دارد. بر اساس action type ما سه کار مختلف را انجام می‌دهیم:

بازگشت state جدید با شمارنده افزایشی

بازگشت state جدید با شمارنده کاهشی

بازگشت state بدون تغییر

ساده است، درسته؟ توجه داشته باشید که چگونه در این فایل‌ها ما هیچ چیزی از redux یا react-redux را ایمپورت نمی‌کنیم، این فقط جاوااسکریپت است.

مرحله 8

وقت آن است که React با Redux ترکیب شود. به src/index.js بروید و محتوای آن را با این قطعه کد جایگزین کنید:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { counterReducer } from './store/reducers';
import App from './App';

const store = createStore(
  counterReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
);

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);

این ورودی برنامه ما است. این برنامه توسط create-react-app تعریف شده است. چند تغییر برای برنامه‌ای که ما در حال ساخت آن هستیم وجود دارد. ما از تابع createStore برای ساخت مخزن‌مان، ارسال reducer به آن استفاده می‌کنیم. سپس App component را در Provider component قرار می‌دهیم که مخزن را برای برنامه Reactمان قابل دسترس می‌کند.

مرحله 9

بیایید کامپوننت Counter را در src/components/Counter.js ایجاد کنیم:

import React from 'react'
import { connect } from 'react-redux'
import { increment, decrement } from '../store/action-creators'
import './counter.css'

export const CounterComponent = ({ counter, handleIncrement, handleDecrement }) => (
  <div>
    <p className="counter">Counter: {counter}</p>
    <button className="btn btn-increment" onClick={handleIncrement}>+</button>
    <button className="btn btn-decrement" onClick={handleDecrement}>-</button>
  </div>
)

CounterComponent یک کامپوننت نمایش‌دهنده است که شمارنده (counter) جاری را نشان می‌دهد و handleIncrement یا handleDecrement را بر روی clickهای button فراخوانی می‌کند. ما این کامپوننت‌های تابع stateless را می‌گیریم و از connect بواسطه react-redux استفاده می‌کنیم تا دسترسی به مخزن ایجاد شود:

const mapStateToProps = ({ counter }) => ({
  counter,
})

const mapDispatchToProps = {
  handleIncrement: increment,
  handleDecrement: decrement,
}

export const Counter = connect(
  mapStateToProps,
  mapDispatchToProps,
)(CounterComponent)

mapStateToProps تابعی است که state را به عنوان آرگومان می‌گیرد. ما از ES6 destructuring استفاده می‌کنیم تا "state.counter" را دریافت کنیم و یک شیء را باز می‌گردانیم که بخشی از stateای که ما نیاز داریم را به props کامپوننت مپ می‌کند.

mapDispatchToProps یک شیء است که سازندگان action را به props کامپوننت مپ می‌کند.

سپس هر دو connect را ارسال می‌کنیم، که یک تابع جدید را بازمی‌گرداند که در CounterComponentمان گرفته خواهد شد و یک کامپوننت متصل شده را بازمی‌گرداند. توجه داشته باشید که کامپوننت ما به طور مستقیم به مخزن دسترسی ندارد؛ connect آن را برای ما زیر نوعی پوشش قرار می‌دهد.

مرحله 10

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

.btn, .counter {
  font-size: 1.5rem;
}

.btn {
  cursor: pointer;
  padding: 0 25px;
}

و سپس محتوای src/App.js را با رندر کامپوننت‌مان جایگزین کنید:

import React, { Component } from 'react';
import { Counter } from './components/Counter'

class App extends Component {
  render() {
    return (
      <div className="App">
        <Counter />
      </div>
    );
  }
}

export default App;

حالا npm را در ترمینال اجرا کنید. درست است. در چند مرحله ساده، ما یک شمارنده را با استفاده از React و Redux ایجاد کردیم. اگر می‌خواهید بدانید چطور برنامه مشابهی را بدون استفاده از Redux پیاده کنید، می‌توانید ریپازیتوری ذکر شده در ابتدای مقاله را clone کرده و آن را بدون redux اجرا کنید.

آیا من همیشه به Redux نیاز دارم؟

قطعا نه. پروژه‌های ساده و کوچک خیلی خوب بدون آن کار می‌کنند. شما دیدید که Redux یک boilerplate کوچک برای تنظیم چیزها اضافه کرد. لازم نیست برنامه ساده خود را بیش از حد پیچیده کنید (مثل کاری که ما انجام دادیم). وقتی که احساس می‌کنید به آن نیاز دارید از آن استفاده کنید. اگر بخش‌های زیادی از state لازم بود تا بین کامپوننت‌هایی که در درخت کامپوننت دور از هم هستند به اشتراک گذاشته شوند و مدیریت state دشوار می‌شود، استفاده از Redux را در نظر بگیرید.

مانند هر ابزار دیگری از آن با احتیاط استفاده کنید. اگر تصمیم گرفتید از React با Redux استفاده کنید، به یاد داشته باشید لازم نیست از آن برای هر قطعه state استفاده کنید. State محلی خوب است. از مخزن برای متغیرهای سراسری استفاده کنید، نه برای همه چیز. همیشه از ابزار مناسب برای کار استفاده کنید.