مدیریت State ها در React با Redux

آموزش قدم به قدم مدیریت State ها با Redux + استفاده از توابع Async در Redux

پیش نیاز:

  • دانش اولیه درباره جاوااسکریپت و React.js

اگر کمی با React کار کرده باشید، بعد از مدتی متوجه میشید که مدیریت State ها در React مسئله بسیار مهمی است. رد و بدل کردن State ها بین Component ها، re-render شدن های اضافی که آپدیت کردن Stateها به سایت شما اضافه می کنه و غیره.

اما با Redux شما می تونید تا حدود زیادی دردسرهای کار با State ها در react رو کم کنید. توی Redux:

  • همه State ها در یک جای به نام store جمع میشن
  • نیازی به رد و بدل کردن مکرر State ها نداریم
  • خود Redux به صورت efficient ای Component ها رو آپدیت می کنه


و اما Redux چیست؟

خیلی مختصر بخوام بگم Redux یک ابزار مدیریت State ها برای React است و بعد از اینکه یه مدت باهاش کار کنید، قدرشو می دونید :)

در ادامه در یک پروژه مختصر قدم با قدم با Redux آشنا خواهیم شد. پیش فرض من اینه که شما با ایجاد پروژه React و خودش آشنایی دارید .


آماده سازی پروژه

با دستور زیر یک پروژه ایجاد می کنیم

npx create-react-app react-redux-tutorial

خب بعدش پروژه تون رو خلوت کنید، پیشفرض من اینه که App.js تون رو این شکلی کردید.

const App= () => {
return( 
<div>
<h1>Hello World</h1>
</div>
)}
export default  App;

افزودن store

با دستور زیر نصب اولیه redux و react-redux رو انجام بدید

npm install redux && npm install react-redux 

حالا نوبت ایجاد store است که محل مدیریت State هاست.

import {createStore} from 'redux'
const store =createStore();
const App= () => {
return (
      <div>
        <h1>Hello  World</h1>
      </div> 
  )
}
export default  App

افزودن Reducer

خب گفتیم store محل مدیریت State هاست، خب پس کی بهش State های برنامه رو برسونه؟

اینجا نوبت به جناب Reducer می رسه که تابعی است که State های برنامه رو تقدیم store می کنه. همچنین فعلا یه متغیر هم درست می کنیم که مثلا state برنامه ماست، اسم متغیر هم می گذاریم initialState.

import {createStore} from 'redux'

const initialState = {username : &quotFarshid&quot};
const returnStateReducer = (state= initialState) =>{
    return state;
  }
const store =createStore(returnStateReducer);
const App= () => {
  return (
      <div>
      <h1>Hello  World</h1>
      </div>
    )
}
export default  App;

همچنین Store را به وسیله Provider به کل برنامه محاط می کنیم.

import {createStore} from 'redux'
import {Provider} from 'react-redux'

const initialState = {username : &quotFarshid&quot};
const returnStateReducer = (state= initialState) =>{
    return state;
}
const store =createStore(returnStateReducer);
const App= () => {
  return (
    <Provider store={store}>
      <div>
        <Header/>
        <h1>Hello  World</h1>
      </div>
    </Provider>
  )
}
export default  App;

خب حالا کل برنامه به State های درون Store دسترسی داره و نیازی به رد و بدل کردن prop ها نیست. حالا برای کامل تر شدن مثال ما یک Component به نام Header اضافه می کنیم.

const Header = ()=> {
  return (
    <div> Header </div>
  )}
export default Header

کد App.js مون هم این شکلی میشه الان

import {createStore} from 'redux'
import {Provider} from 'react-redux'
import Header from './Header'

const initialState = {username : &quotFarshid&quot};
const returnStateReducer = (state= initialState) =>{
    return state;
}
const store =createStore(returnStateReducer);
const App= () => {
  return (
    <Provider store={store}>
      <div>
        <Header/>
        <h1>Hello  World</h1>
      </div>
    </Provider>
  )}
export default  App;

بزن بریم ...

خب حالا همانطور که حدس زدید، میریم که از Header به username دسترسی پیدا کنیم. تابعی که react-redux برای دسترسی به State ها به ما معرفی می کنه، اسمش useSelector هست. به عبارت دیگر بوسیله این تابع می تونیم به Store برنامه مون یه سری بزنیم و State مدنظرمون رو از فروشگاه انتخاب کنیم!

const Header = ()=> {
  const user = useSelector((state) => state.username);
  return (
    <div>{user} </div>
  )
}
export default Header

خب حالا همچین چیزی داریم

گفتیم که Reducer جایی است که State های ما رو تحویل store می ده، جالبه بدونید که جناب Reducer علاوه بر وظیفه ای که گفتم، می تونه State رو آپدیت هم کنه و بعد تحویل Store بده. برای آپدیت State تابع Reducer ورودی به نام action در نظر گرفته است.

import { createStore } from &quotredux&quot
import { Provider } from &quotreact-redux&quot
import Header from &quot./Header&quot

const initialState = { username: &quotFarshid&quot };
const returnStateReducer = (state = initialState, action) => { 
if (action.type === &quotupdateUser&quot) {
 return { ...state, username: &quotFarhad&quot }; 
} else {
  return state;
 }
};
const store = createStore(returnStateReducer);
const App = () => {
 return (
<Provider store={store}>
 <div>
   <Header />
    <h1>Hello World</h1>
 </div>
  </Provider>
);
};
export default App;

برای اینکه بفهمیم چه تغییر دلخواهی قراره روی State با action بدیم یک type برای action مون تعیین می کنیم. در کد بالا در صورتی که action.type برابر updateUser باشد، سایر State ها غیر username دست نخورده تحویل داده می شوند (با ...) و username به فرهاد تغییر می کند.

استفاده از action برای آپدیت State

حالا میریم توی Header.js و یک button می گذاریم که به وسیله اون بتونیم action گفته شده در بالا(updateUser) را فراخوانی کنیم. تابعی که می تواند State ما رو آپدیت کنه و تحویل Reducer مون بده یه hook به نام useDispatch است.

import {useDispatch, useSelector} from 'react-redux'

const Header = ()=> {
  const user = useSelector((state) => state.username);
  const dispatch = useDispatch();
  return (
    <div>{user}
    <button ={()=> dispatch({type: &quotupdateUser&quot})}>Change User</button>
    </div>
  )
}
export default Header

خب حالا اگر روی دکمه کلید کنید اسم Farshid به Farhad تغییر می کند.

تبریک به من، تبریک به تو، تبریک به همه! موفق شدیم که State مون رو با Redux آپدیت کنیم. اینکار خیلی راحتتر میشه براتون وقتی چند بار تمرین کنه، فقط کافیه مفاهیم Store و Reducer و Dispatch رو خیلی ساده برای خودتون مرور کنید.

آپدیت State با ورودی کاربر

حالا می خواهیم با ورودی که از کاربر می گیریم username رو آپدیت کنیم. برای اینکار کافیه بدونیم که action علاوه بر type یه ویژگی به payload داره که می تونیم محتوایی که قراره جایگزین بشه رو هم به Reducer بده.

خب حالا ما یک type جدید اولا اضافه می کنیم که برای آپدیت State با ورودی کاربره و از اونور هم payload رو از input گرفته و dispatch می کنیم.

import {useDispatch, useSelector} from 'react-redux'

const Header = ()=> {
  const user = useSelector((state) => state.username);
  const dispatch = useDispatch();
return (
    <div>{user}
    <input ={(e)=> dispatch({type: &quotupdateMyName&quot, payload: e.target.value}) }>
    </input>
    <button ={()=> dispatch({type: &quotupdateUser&quot})}>Change User</button>
    </div>
  )}
export default Header


import {createStore} from 'redux'
import {Provider} from 'react-redux'
import Header from './Header'

const initialState = {username : &quotFarshid&quot};
const returnStateReducer = (state= initialState, action) =>{
  if(action.type === &quotupdateUser&quot){
    return {...state, username : &quotFarhad&quot};
  }else if(action.type === &quotupdateMyName&quot){
    return {...state, username : action.payload};
  }else {
    return state;
  }}
const store =createStore(returnStateReducer);
const App= () => {
  return (
    <Provider store={store}>
      <div>
        <Header/>
        <h1>Hello  World</h1>
      </div>
    </Provider>
  )}
export default  App;

حالا اسممون رو می تونیم آپدیت کنیم.

توابع Async در Redux

حالا فرض کنیم شما می خواهید یک سری دیتا از یک API گرفته و بعد State خودتون رو آپدیت کنید. از اونجا که این یک فرآیند Async است و Redux اساسا طوری ساخته شده که Sync کار کنه، نیاز به یک واسط داریم تا توانایی Async رو به Redux اضافه کنه که اسمش Redux-thunk می باشه!

برای استفاده ازش ابتدا با دستور npm i redux-thunk نصبش می کنیم. حالا برای اینکه به store مون بگیم قراره به صورت Async با State هاش کار بشه از متد applyMiddleware در redux استفاده می کنیم، به شکل زیر:

import {createStore, applyMiddleware} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import Header from './Header'

const initialState = {username : &quotFarshid&quot};
const returnStateReducer = (state= initialState, action) =>{
  if(action.type === &quotupdateUser&quot){
    return {...state, username : &quotFarhad&quot};
  }else if(action.type === &quotupdateMyName&quot){
    return {...state, username : action.payload};
  }else {
    return state;
  }}
const store =createStore(returnStateReducer, applyMiddleware(thunk));
const App= () => {
  return (
    <Provider store={store}>
      <div>
        <Header/>
        <h1>Hello  World</h1>
      </div>
    </Provider>
  )}
export default  App;

برای گرفتن کاربر از API، ما از API رایگان https://randomuser.me/ استفاده می کنیم که به راحتی میشه ازش اطلاعات کاربری fake گرفت و در کارهای توسعه استفاده کرد.

حالا بیایین یکم مسئله رو ساده کنیم!

فرض کنید ما به store مون (فروشگاهمون!) می خواهیم یه سری اطلاعات رو از یه راه دور بدیم، اینجا سه تا فاز داریم که معادل سه State می شوند

  • گرفتن اطلاعات یا Loading
  • اطلاعات به درستی دریافت شدند
  • با خطا مواجه شدیم

خب حالا کدش اینجوری میشه:

import {createStore, applyMiddleware} from 'redux'
import {Provider} from 'react-redux'
import thunk from 'redux-thunk'
import Header from './Header'

const initialState = {username : &quotFarshid&quot};
const returnStateReducer = (state= initialState, action) =>{
  if(action.type === &quotupdateUser&quot){
    return {...state, username : &quotFarhad&quot};
  }else if(action.type === &quotupdateMyName&quot){
    return {...state, username : action.payload};
  }else if(action.type === &quotloadingUser&quot){
    return {...state, username : &quotLoading...&quot};
  }else if(action.type === &quotasyncUser&quot){
    return {...state, username : action.payload};
  }else if(action.type === &quoterror&quot){
    return {...state, username : &quoterror&quot};
  }else {
    return state;  
}
}

const store =createStore(returnStateReducer, applyMiddleware(thunk));
const App= () => {
  return (
    <Provider store={store}>
      <div>
        <Header/>
        <h1>Hello  World</h1>
      </div>
    </Provider>
  )}
export default  App;

حالا بریم سمت Header.js یعنی جایی که قراره اطلاعات از API گرفته بشه و فرستاده بشه برای Store ما. اولا یک دکمه اضافه می کنیم که تریگر ما برای گرفتن دیتا از API باشد. همانطور که قبلا گفتیم dispatch خبرچینی است که تغییر State رو به گوش Store می رسونه :)

حال ما نیاز داریم تا تابعی بسازیم که براساس وضعیت اخذ داده از API برای ما dispatch تولید کنه یا به عبارتی خبر ببره. این کار رو با تابع asyncRandomUser انجام می دیم. با کلید کاربر روی دکمه مربوطه هم خود این تابع رو dispatch می کنیم تا به گوش store برسه.

import {useDispatch, useSelector} from 'react-redux'

const asyncRandomUser= () =>{
        return async function (dispatch){
          dispatch({type:&quotloadingUser&quot});
          try{
              const request = await fetch(&quothttps://randomuser.me/api&quot)
              const userData = await request.json();
              console.log(&quotGot user&quot, userData);
              dispatch({type:&quotasyncUser&quot, payload: userData.results[0].name.first})
          }catch(error){
            dispatch({type:&quoterror&quot})
          }
        }
}

const Header = ()=> {
  const user = useSelector((state) => state.username);
  const dispatch = useDispatch();
  return (
    <div>{user}
    <input ={(e)=> dispatch({type: &quotupdateMyName&quot, payload: e.target.value}) }>
    </input>
    <button ={()=> dispatch({type: &quotupdateUser&quot})}>Change User</button>
    <button ={()=> dispatch(asyncRandomUser())}>Fetch User</button>
    </div>
  )}
export default Header
پایان!
پایان!

خسته نباشید به همگی، ما تونستیم هم به صورت سنکرون و هم آسنکرون State ها رو با Redux مدیریت کنیم. به همین راحتی!

از توجه تون متشکرم!

ممنون میشم اگر ایراد فنی یا تایپی وجود داشت بهم اطلاع بدید تا اصلاحش کنم. امیدوارم براتون مفید بوده باشه :)