افزودن Action ها در Redux و React

31 فروردین 1398
درسنامه درس 20 از سری آموزش react (ری اکت)
React-redux-actions

در درس قبلی بخش مشکل یکپارچه کردن برنامه ری اکت با Redux را انجام دادیم. در این درس، یک سری قابلیت برای تنظیمات Redux تعریف می کنیم.

تا به اینجا برنامه ای که ایجاد کردیم می تواند به خوبی زمان جاری را نشان می دهد. اما هیچ راهی برای بروزرسانی یک زمان جدید وجود ندارد، که در این درس می خواهیم این مشکل را حل کنیم.

انجام بروزرسانی ها

به خاطر داشته باشید که تنها با استفاده از یک action creator می توانیم داده ها را در Redux تغییر دهیم. در درس قبلی یک redux store را ایجاد کردیم، اما هیچ راه حلی برای بروزرسانی این store را ایجاد نکردیم.

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

  • ایجاد یک action creator برای ارسال اکشن به Store
  • فراخوانی actionCreator onclick یک عنصر
  • مدیریت اکشن در reducer

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

در درس قبلی، ما گفتیم که اکشن ها چه چیزی هستند، اما نگفتیم که چرا باید از actionCreator ها استفاده کنیم، و یا اصلاً اینها چه چیزی هستند.

به بیان ساده، اکشن یک آبجکت ساده است که باید یک مقدار type داشته باشد. قبلاً یک فایل به نام types.js ایجاد کردیم که ثابت های action type یا نوع اکشن ها را نگهداری می کرد، بنابراین می توانیم از این مقادیر به عنوان پروپرتی type استفاده کنیم.

export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';

همان طور که گفتیم، اکشن ها می توانند هر نوع آبجکتی که شامل یک کلید type هستند، باشند، همچنین می توانیم داده ها را از طریق این اکشن ها ارسال کنیم (به طور قراردادی، داده های اضافی توسط پروپرتی payload یک اکشن ارسال می کنیم)

{
  type: types.FETCH_NEW_TIME,
  payload: new Date().toString()
}

حال باید این اکشن را به Store نرم افزار ارسال (dispatch) کنیم:

store.dispatch({
  type: types.FETCH_NEW_TIME,
  payload: new Date().toString()
})

اما این کار یک راه حل خوب نیست. بجای اینکه یک اکشن را به طور مستقیم ارسال کنیم، بهتر است از یک تابع که یک اکشن را بر می گرداند، به جای اینکار استفاده نماییم، یعنی تابعی که یک اکشن را ایجاد خواهد کرد (بخاطر همین به آن actionCreator می گویند).

این روش از نظر تست پذیری، قابلیت استفاده مجدد، مستندسازی و کپسوله سازی منطق برنامه، نسبت به روش قبلی برتری دارد.

حال اولین actionCreator خود را در فایل redux/actionCreator.js ایجاد می کنیم.

در این فایل یک تابع که مسئولیت برگرداندن یک اکشن برای ارسال به store است را تعریف و سپس export می کنیم.

import * as types from './types';

export const fetchNewTime = () => ({
  type: types.FETCH_NEW_TIME,
  payload: new Date().toString(),
})

export const login = (user) => ({
  type: types.LOGIN,
  payload: user
})

export const logout = () => ({
  type: types.LOGOUT,
})

حال اگر این تابع را فراخوانی کنیم، یک آبجکت را به ما بر می گرداند. اما چطور باید این اکشن را گرفته و به Store ارسال کنیم.

اگر به یاد داشته باشید، در درس قبلی از تابع ()connect که از پکیج react-redux، export شده بود، استفاده کردیم. اولین آرگومان آن mapStateToProp بود که state را به یک آبجکت prop نگاشت (تبدیل) می کرد. اما این تابع یک آرگومان دوم هم می گیرد که به ما اجازه می دهد تا به راحتی توابع را به یک props، نگاشت کنیم.

حال اجازه بدهید به صورت عملی این قابلیت را به شما نشان دهیم. در فایل src/views/Home/Home.js با ارائه یک تابع دیگر برای استفاده از actionCreator که قبلاً ایجاد کرده ایم، فراخوانی متد ()connect را بروزرسانی می کنیم.

در پارامتر دوم تابع connect، متد mapDispatchToProps را فراخوانی می کنیم.

import { fetchNewTime } from '../../redux/actionCreators';
  // ...
const mapDispatchToProps = dispatch => ({
  updateTime: () => dispatch(fetchNewTime())
})
  // ...
export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(Home);

حال، تابع ()updateTime به عنوان یک props به آن پاس داده شد و هنگامی که یک action اتفاق بیفتد، متد ()dispatch فراخوانی می شود. حال کامپوننت <Home/> را بروزرسانی می کنیم و یک دکمه در آن قرار می دهیم تا کاربر با فشار دادن این دکمه، زمان را بروزرسانی کند.

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

اگر چه این مثال خیلی جذاب به نظر نمی رسد، اما به خوبی ویژگی و قابلیت های Redux را نشان می دهد. به طور مثال می توانیم یک دکمه داشته باشیم و با فشار دادن آن یک توئیت جدید از سرور واکشی شود یا از یک اتصال سوکت برای بروزرسانی redux store استفاده کنیم. همین مثال ساده، قابلیت های کامل Redux را نشان می دهد.

reducer های چند گانه

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

Redux این مشکل را برای شما حل می کند. Redux قابلیتی دارد که توسط آن می توانید reducerهای برنامه تان را به چندین reducer مجزا تقسیم کنید که هر کدام از این Reducerها مسئولیت یک بخش از ساختار stateها را بر عهده داشته باشد.

ما می توانیم از تابع ()combineReducers که در پکیج redux قرار گرفته برای نوشتن یک آبجکتی از توابع reducer استفاده کنیم.

سپس بعد از این که اکشنی اتفاق افتاد، هر کدام از این توابع reducer متناسب با اکشن اتفاق افتاده، اجرا می شود. حال می خواهیم بصورت عملی این قابلیت را بررسی کنیم.

فرض کنید که می خواهیم وضعیت کاربر فعلی را بررسی کنیم. برای این منظور یک ماژول Redux به نام currentUser در مسیر src/redux/currentUser.js ایجاد می کنیم.

touch src/redux/currentUser.js

حال ما همان چهار مقداری که از ماژول currentTime استخراج کرده بودیم را در اینجا export می کنیم، اما در اینجا به طور مشخص برای کاربر فعلی استفاده می کنیم.

پس یک ساختار ساده برای مدیریت یک کاربر اضافه می کنیم:

import * as types from './types'

export const initialState = {
  user: {},
  loggedIn: false
}

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    case types.LOGIN:
      return {
        ...state, user: action.payload, loggedIn: true};
    case types.LOGOUT:
      return {
        ...state, user: {}, loggedIn: false};
    default:
      return state;
  }
}

حال تابع ()configureStore را برای در نظر گرفتن این تقسیم بندی ها و با استفاده از combineReducer ها دو دسته بندی را از آن جدا می کنیم.

import { createStore, combineReducers } from 'redux';

import { rootReducer, initialState } from './reducers'
import { reducer, initialState as userInitialState } from './currentUser'

export const configureStore = () => {
  const store = createStore(
    combineReducers({
      time: rootReducer,
      user: reducer
    }), // root reducer
    {
      time: initialState,
      user: userInitialState
    }, // our initialState
  );

  return store;
}

export default configureStore;

حال می توانیم actionCreatorهای ()login و ()logout را برای ارسال اکشن ها به store ایجاد کنیم.

export const login = (user) => ({
  type: types.LOGIN,
  payload: user
})
  // ...
export const logout = () => ({
  type: types.LOGOUT,
})

حال ما می توانیم از actionCreatorها برای فراخوانی login و logout همانند تابع ()updateTime استفاده کنیم.

در این درس، چرخه بین بروزرسانی و ذخیره داده ها در Redux state سراسری را کامل کردیم. به علاوه چطور Redux را توسعه داده تا بتوانیم از چندین Reducer و اکشن به همراه چندین کامپوننت متصل شده، استفاده کنیم.

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

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

دیدگاه‌های شما

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