آموزش قدم به قدم مدیریت State ها با Redux + استفاده از توابع Async در Redux
پیش نیاز:
اگر کمی با React کار کرده باشید، بعد از مدتی متوجه میشید که مدیریت State ها در React مسئله بسیار مهمی است. رد و بدل کردن State ها بین Component ها، re-render شدن های اضافی که آپدیت کردن Stateها به سایت شما اضافه می کنه و غیره.
اما با Redux شما می تونید تا حدود زیادی دردسرهای کار با State ها در react رو کم کنید. توی Redux:
و اما 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 : "Farshid"}; 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 : "Farshid"}; 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 : "Farshid"}; 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 "redux" import { Provider } from "react-redux" import Header from "./Header" const initialState = { username: "Farshid" }; const returnStateReducer = (state = initialState, action) => { if (action.type === "updateUser") { return { ...state, username: "Farhad" }; } 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: "updateUser"})}>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: "updateMyName", payload: e.target.value}) }> </input> <button ={()=> dispatch({type: "updateUser"})}>Change User</button> </div> )} export default Header
import {createStore} from 'redux' import {Provider} from 'react-redux' import Header from './Header' const initialState = {username : "Farshid"}; const returnStateReducer = (state= initialState, action) =>{ if(action.type === "updateUser"){ return {...state, username : "Farhad"}; }else if(action.type === "updateMyName"){ 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 : "Farshid"}; const returnStateReducer = (state= initialState, action) =>{ if(action.type === "updateUser"){ return {...state, username : "Farhad"}; }else if(action.type === "updateMyName"){ 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 می شوند
خب حالا کدش اینجوری میشه:
import {createStore, applyMiddleware} from 'redux' import {Provider} from 'react-redux' import thunk from 'redux-thunk' import Header from './Header' const initialState = {username : "Farshid"}; const returnStateReducer = (state= initialState, action) =>{ if(action.type === "updateUser"){ return {...state, username : "Farhad"}; }else if(action.type === "updateMyName"){ return {...state, username : action.payload}; }else if(action.type === "loadingUser"){ return {...state, username : "Loading..."}; }else if(action.type === "asyncUser"){ return {...state, username : action.payload}; }else if(action.type === "error"){ return {...state, username : "error"}; }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:"loadingUser"}); try{ const request = await fetch("https://randomuser.me/api") const userData = await request.json(); console.log("Got user", userData); dispatch({type:"asyncUser", payload: userData.results[0].name.first}) }catch(error){ dispatch({type:"error"}) } } } const Header = ()=> { const user = useSelector((state) => state.username); const dispatch = useDispatch(); return ( <div>{user} <input ={(e)=> dispatch({type: "updateMyName", payload: e.target.value}) }> </input> <button ={()=> dispatch({type: "updateUser"})}>Change User</button> <button ={()=> dispatch(asyncRandomUser())}>Fetch User</button> </div> )} export default Header
خسته نباشید به همگی، ما تونستیم هم به صورت سنکرون و هم آسنکرون State ها رو با Redux مدیریت کنیم. به همین راحتی!
از توجه تون متشکرم!
ممنون میشم اگر ایراد فنی یا تایپی وجود داشت بهم اطلاع بدید تا اصلاحش کنم. امیدوارم براتون مفید بوده باشه :)