آموزش Redux در ری اکت

02 اردیبهشت 1398
درسنامه درس 19 از سری آموزش react (ری اکت)
React-redux

در درس قبلی درباره الگوی Flux و کتابخانه های مختلفی که از این الگو استفاده می کنند (مانند Redux) صحبت کردیم. در این درس قصد داریم کدنویسی را شروع و Redux را به برنامه خود اضافه کنیم. این پروژه یک برنامه خیلی ساده ای است که تنها زمان جاری را از سرور گرفته و نمایش می دهد. برای سادگی کار، فعلاً نمی خواهیم داده ها را از یک سرور خارجی دریافت کنیم و این کار را تنها با آبجکت Date جاوا اسکریپت انجام می دهیم.

آموزش Redux

اولین قدم برای استفاده از Redux ، نصب آن است، برای نصب این کتابخانه از npm استفاده می کنیم. ترمینال را باز کرده و به مسیر پروژه بروید، سپس دستور زیر را برای نصب Redux اجرا کنید:

npm install --save redux

برای استفاده از redux نیاز به نصب پکیج دیگری به نام react-redux داریم. این پکیج به ما کمک می کند تا react و redux را به هم متصل کنیم.

npm install --save react-redux
نصب react-redux
نصب react-redux

پیکربندی

در قدم بعدی باید پیکربندی های زیر را انجام دهیم:

  • تعریف reducer
  • ایجاد یک store
  • ایجاد actionCreators
  • اتصال store به ویوهای ریکت
  • استفاده از مزیت Redux

در ادامه این درس راجع به این اصطلاحات فنی صحبت خواهیم کرد. بنابراین ساختار برنامه را از نو ایجاد می کنیم، سپس یک کامپوننت wrapper به منظور فراهم سازی داده برای برنامه خود بوجود می آوریم.

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

[Root] -> [App] -> [Router/Routes] -> [Component]

بدون هر گونه معطلی، فایل src/App.js را به دایرکتوری src/containers منتقل کنید و تعدادی از مسیرهای پکیج هایی که import کرده ایم را هم باید بروزرسانی کنیم. همان طور که در چند درس قبلی هم گفتیم، ما از react router material استفاده می کنیم.

ما چند روت را داخل دستور <switch> می نویسیم تا مطمئن شویم که هر بار تنها یک روت نمایش داده می شود:

import React from 'react';

import {
  BrowserRouter as Router,
  Route,
  Switch
} from 'react-router-dom'

// We'll load our views from the `src/views`
// directory
import Home from './views/Home/Home';
import About from './views/About/About';

const App = props => {
  return (
    <Router>
      <Switch>
        <Route
          path="/about"
          component={About} />
        <Route
          path="*"
          component={Home} />
      </Switch>
    </Router>
  )
}

export default App;

همچنین باید یک کامپوننت محصور کننده (container) به نام Root ایجاد کنیم تا بتوانیم کامپوننت <App> را داخل این کامپوننت Root قرار دهیم. برای اینکار فایل src/container/Root.js را ایجاد کنید:

touch src/containers/Root.js

در حال حاضر، از یک کامپوننت موقتی استفاده می کنیم، اما در ادامه که راجع به store ها صحبت کردیم، محتوای این کامپوننت را تغییر می دهیم:

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

const Root = (props) => {
  return (
    <App />
  );
}

export default Root;

در انتها، فایل src/index.js را باز کنید و کامپوننت <Root> را به جای کامپوننت <App> که قبلاً استفاده می شد، جایگزین کنید.

import React from 'react';
import ReactDOM from 'react-dom';
import Root from './containers/Root';
import './index.css';

ReactDOM.render(
  <Root />,
  document.getElementById('root')
);

اضافه کردن Redux

حال که ساختار اولیه برنامه مان را شکل دادیم، می توانیم Redux را اضافه کنیم. مراحلی که برای استفاده از Redux در هر برنامه ای باید طی شود، مشابه زیر است:

  • نوشتن یک rootReducer
  • نوشتن actionCreator
  • پیکربندی Store با rootReducer ،store و app
  • اتصال ویوها به actionCreator

حال قدم به قدم مراحل فوق را توضیح می دهیم:

برای شروع باید ساختار برنامه را برای اضافه کردن redux تنظیم می کنیم، اکثر کارها را در دایرکتوری src/redux انجام می دهیم. پس ابتدا این دایرکتوری را ایجاد می کنیم:

mkdir -p src/redux
touch src/redux/configureStore.js
touch src/redux/reducers.js
اضافه کردن Redux به ری اکت
اضافه کردن Redux به ری اکت

در ابتدا باید reducer را ایجاد کنیم. هر چند که شاید فکر کنید Reducer یک چیز پیچیده ای است، اما باید بدانید که Reducer فقط یک تابع است. وظیفه Reducer برگرداندن یک نمایشی از وضعیت (state) بعدی است.

برخلاف flux در الگوی Redux تنها یک Store سراسری برای مدیریت وضعیت های برنامه باید داشته باشیم.

اینکار باعث می شود تا بتوانیم با تمام داده های برنامه به راحتی کار کنیم.

تابع reducer root مسئولیت برگرداندن نمایشی از وضعیت سراسری فعلی برنامه را بر عهده دارد.

هنگامی که یک action را روی Store ارسال می کنیم، تابع reducer با وضعیت فعلی برنامه فراخوانی می شود و این اکشن باعث بروزرسانی وضعیت می شود.

حال فایل src/redux/reducers.js را باز کرده و rootReducer خود را در آن ایجاد می کنیم:

// Initial (starting) state
const initialState = {
  currentTime: new Date().toString()
}

// Our root reducer starts with the initial state
// and must return a representation of the next state
const rootReducer = (state = initialState, action) => {
  return state;
}

export default rootReducer

در آرگومان اول این تابع وضعیت ابتدایی را تعریف می کنیم (در دفعه اول اجرا، rootReducer بدون آرگومان فراخوانی می شود، بنابراین از initialState به عنوان وضعیت اولیه تابع استفاده می شود).

اکشن (Action) در ری اکت چیست؟

آرگومان دوم یک اکشن است که از یک Store ارسال شده است.

در ادامه راجع به این بیشتر صحبت می کنیم:

یک اکشن حداقل، باید یک نوع کلیدی داشته باشد، این نوع هر مقداری که بخواهیم می تواند داشته باشد، اما آن مقدار باید وجود داشته باشد.

برای مثال، گاهی اتفاق می افتاد که ما یک اکشن را ارسال می کنیم تا به store بگوییم که زمان جاری را برای ما برگرداند. به عنوان مثال می توانیم این اکشن را با یک مقدار رشته ای به نام FETCH_NEW_TIME فراخوانی می کنیم.

اکشنی که ما از Store خود برای کنترل این بروزرسانی ارسال می کنیم، مطابق زیر است:

{
  type: 'FETCH_NEW_TIME'
}

چون ما از این رشته به طور مکرر در برنامه استفاده می کنیم، احتمال دارد حین تایپ دستور دچار خطاهای تایپی شویم. برای جلوگیری از این کار، یک فایل به نام type.js ایجاد کرده و نوع اکشن را به عنوان یک متغیر ثابت Export می کنیم. حال یک فایل به نام src/redux/types.js ایجاد کرده و کدهای زیر را در آن قرار دهید:

export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';

حال به جای اینکه مقدار رشته FETCH_NEW_TIME را به صورت دستی وارد کنیم، یک ارجاع به این رشته از فایل types.js را می دهیم.

import * as types from './types';

{
  type: types.FETCH_NEW_TIME,
}

هنگامی که بخواهیم یک داده همراه با اکشن ارسال کنیم، می توانیم هر کلیدی که بخواهیم را به این اکشن اضافه نماییم.

در اینجا نامش را payload می گذاریم، اما شما می توانید نام آن را هر چه که بخواهید بگذارید. اما به طور قراردادی برای فراخوانی اطلاعات اضافی از نام payload استفاده می کنیم.

اکشن FETCH_NEW_TIME یک payload با زمان جاری جدید را ارسال خواهد کرد. چون می خواهیم یک مقدار قابل serialize کردن را به اکشن ارسال می کنیم، مقدار رشته ای برابر زمان جاری جدید را ارسال خواهیم کرد:

{
  type: types.FETCH_NEW_TIME,
  payload: new Date().toString() // Any serializable value
}

حال در داخل متد reducer می توانیم نوع اکشن را بررسی کرده و مطابق با هر کدام از نوع اکشن ها یک State جدید ایجاد کنیم. در این مثال، فقط payload را ذخیره می کنیم. در واقع می خواهیم بگوییم:‌ اگر نوع (type) اکشن برابر FETCH_NEW_TIME بود، زمان جاری (current time) جدید را از اکشن payload برگرداند.

export const rootReducer = (state = initialState, action) => {
  switch(action.type) {
    case types.FETCH_NEW_TIME:
      return { ...state, currentTime: action.payload}
    default:
      return state;
  }
}

دقت کنید که reducerها باید یک وضعیت (state) را بر گردانند و در حالت پیش فرض مطمئن شوید که حداقل State جاری را باید توسط آنها بازگردانده شود.

نکته مهم در Redux

چون توابع Reducer در هر بار ارسال یک اکشن، اجرا می شوند، باید اطمینان حاصل کنیم که این توابع تا حد امکان ساده و سریع نوشته شوند و نمی خواهیم که تاخیر زیادی در اجرای آن بوجود بیاید یا باعث بروز تاثیرات جانبی ناخواسته در برنامه شوند.

همچنین تاثیرات جانبی را خارج از reducer ها و در actionCreator ها، مدیریت می کنیم.

قبل از اینکه actionCreator ها را بررسی کنیم (و این که اصلاً چرا به آنها actionCreator می گویند)، باید Store را به برنامه وصل می کنیم.

برای اتصال ویوهای برنامه به redux store از پکیج react-redux استفاده می کنیم. ابتدا باید این پکیج را با دستور زیر نصب کنیم:

npm install --save react-redux

اتصال Store به ویوهای برنامه در ری اکت

پکیج react-redux یک کامپوننت به نام provider را Export می کند.

کامپوننت provider به ما این امکان را می دهد تا بتوانیم از Store در تمام کامپوننت های Container در برنامه استفاده کنیم.

کامپوننت provider یک پروپرتی store را به عنوان آرگومان می گیرد که باید یک redux store معتبر را به این پروپرتی پاس بدهیم، سپس باید تابع configureStore را تعریف کنیم تا برنامه بدون مشکل اجرا می شود.

برای شروع، ابتدا کامپوننت provider را وارد برنامه می کنیم.

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

import { Provider } from 'react-redux';
  // ...
const Root = (props) => {
  // ...
  
  return (
    <Provider store={store}>
      <App />
    </Provider>
  );
}

دقت کنید که ما مقدار Store را به کامپوننت provider ارسال می کنیم، اما هنوز Store را ایجاد نکرده ایم که الان اینکار را انجام می دهیم.

پیکربندی Store در Redux

برای ساخت Store یک فایل به نام src/redux/configureStore.js ایجاد کرده و یک تابع که مسئولیت ایجاد Store را دارد، از این کامپوننت export می کنیم.

چطور باید یک Store را ایجاد کنیم؟

پکیج redux متدی به نام createStore را export می کند که می توانیم از آن برای ساخت یک store استفاده کنیم، برای اینکار فایل src/redux/configureStore.js را باز کنید و تابع ()configureStore را از آن export کرده و تابع کمک کننده (هلپر) CreateStore را وارد (import) می کنیم:

import {createStore} from 'redux';
  // ...
export const configureStore = () => {
  // ...
}
  // ...
export default configureStore;

فعلاً چیزی را از این Store بر نمی گردانیم و فقط یک store را با استفاده از تابع ()createStore ایجاد می کنیم:

import {createStore} from 'redux';

export const configureStore = () => {
  const store = createStore();

  return store;
}

export default configureStore;

اگر صفحه را در مرورگر بارگزاری کنید، صفحه رندر نخواهد داشت و با خطاهای زیر روبرو خواهید شد.

پیکربندی Store در Redux
پیکربندی Store در Redux

این خطا به ما می گوید که ما هیچ reducerیی داخل Store نداریم. بدون یک reducer، Store نمی داند که با اکشن ها چکار کند یا چطور وضعیت (state) را ایجاد کند و ... .

برای رفع این مشکل باید ارجاع به rootReducerیی که قبلاً آن را ایجاد کرده بودیم، تعریف کنیم.

تابع createStore در آرگومان اولش یک reducer می گیرد که ما rootRedcer را به آن پاس می دهیم.

همچنین در آرگومان دوم، وضعیت (state) اولیه را دریافت می کند. هر دوی این مقادیر را از فایل reducers.js که قبلاً آن را ایجاد کرده بودیم، وارد میکنیم:

import { rootReducer, initialState } from './reducers'
  // ...
export const configureStore = () => {
  const store = createStore(
    rootReducer, // root reducer
    initialState, // our initialState
  );

  return store;
}

حال فایل Root.js را با یک نمونه از Store که با فراخوانی تابع ()configureStore ایجاد شده است، بروزرسانی می کنیم:

const Root = (props) => {
  const store = configureStore();
  
  return (
    <Provider store={store}>
      <App />
    </Provider>
  );
}

اتصال ویوها به ری اکت و Redux

تا به اینجا توانستیم برنامه مان را برای استفاده از Redux پیکربندی کنیم. یک راه دیگر که redux پیشنهاد می دهد، اتصال بخش هایی از درخت state به کامپوننت های متفاوت با استفاده از تابع ()connect است که این تابع از پکیج react-redux استخراج (export) شده است.

تابع ()connect یک تابعی که آرگومان اول آن یک کامپوننت است را بر می گرداند.

تابع ()connect از ما انتظار دارد تا حداقل یک آرگومان به آن پاس بدهیم. اولین آرگومان یک تابع است که همراه با state فراخوانی می شود و یک آبجکتی که داده ها را به ویو متصل می کند را بر می گرداند.

نام این تابع را mapStateToProps می گذاریم. چون این تابع مسئولیت نگاشت State به یک آبجکتی که به یک props اصلی کامپوننت متصل شده را بر عهده دارد.

حال یک ویو به نام Home در مسیر src/views/Home.js ایجاد کرده و از تابع ()connect برای اتصال مقدار currentTime به درخت State مان استفاده می کنیم:

import { connect } from 'react-redux';
  // ...
const mapStateToProps = state => {
  return {
    currentTime: state.currentTime
  }
}
export default connect(
  mapStateToProps
)(Home);

تابع ()connect به طور خودکار هر تعداد کلید در آرگومان اول تابع را به عنوان props به کامپوننت Home ارسال می کند.

در این برنامه، پروپرتی currentTime در کامپوننت Home به کلید CurrentTime مربوط به state نگاشت می شود.

حال کامپوننت Home را به منظور نمایش مقدار currentTime بروررسانی می کنیم:

const Home = (props) => {
  return (
    <div className="home">
      <h1>Welcome home!</h1>
      <p>Current time: {props.currentTime}</p>
    </div>
  );
}

اگر چه این برنامه خیلی جذاب نیست، اما نشان می دهد که چطور برنامه Redux مان را با data های ارسال شده به State سراسری پیکربندی کنیم و همچنین نحوه نگاشت داده ها توسط کامپوننت ها را هم توضیح دادیم.

در درس بعدی توسط actionCreator ها وضعیت سراسری برنامه را بروزرسانی می کنیم و همچنین درباره نحوه ترکیب چندین ماژول Redux به هم توضیح خواهیم داد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش react (ری اکت) توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.