در درس قبلی، Redux را به برنامه خود اضافه کردیم، با reducer ها کار کردیم، actionCreator را بروزرسانی و Redux را به کامپوننت های ری اکت متصل کردیم. در این درس با یکی دیگر از قابلیت های قدرتمند Redux به نام Redux middleware آشنا خواهیم شد.
middleware ها معمولا به سرویس های نرم افزاری گفته می شوند که قابلیت های مختلف نرم افزار را به هم متصل می کند.
در redux، middleware ها به عنوان یک لایه اضافی بین اکشن ارسال شده و Reducer قرار می گیرد:
[ Action ] <-> [ Middleware ] <-> [ Dispatcher ]
به عنوان مثال لاگین کردن، گزارش های خطا، مسیریابی، مدیریت درخواست های غیرهمزمان همگی نمونه هایی از middleware هستند.
مدیریت درخواست های غیرهمزمان، مثل یک درخواست http به یک سرور را در نظر بگیرید. middleware ها یکی از بهترین قابلیت هایی است که می تواند در انجام اینکار به ما کمک کند.
ما می خواهیم چند middleware برای مدیریت ایجاد درخواست های غیرهمزمان در برنامه مان ایجاد کنیم. middlewareها بین اکشن و reducer ها قرار می گیرند. middleware ها اکشن های ارسال شده را گرفته و کدهای آنها به همراه جزئیات اکشن و state های فعلی را اجرا می کنند.
middleware ها یک انتزاع قدرتمند را ارائه می دهند. بیایید ببینیم که چطور باید از آنها برای مدیریت برنامه خود استفاده کنیم.
در ادامه کار با currentTime redux که در درس قبلی روی آن کار می کردیم، یک middleware برای واکشی زمان جاری از سروری که در چند درس قبلی زمان جاری را از آن دریافت می کردیم، ایجاد می کنیم.
قبل از شروع کار، باید currentTime
را از rootReducer
که در فایل reducer.js
قرار دارد گرفته و به فایل خودمان اضافه کنیم.
برای شروع، ابتدا با فایل redux/currentTime.js
کار می کنیم. در این فایل دو آبجکت را export می کنیم:
import * as types from './types'; export const initialState = { currentTime: new Date().toString(), } export const reducer = (state = initialState, action) => { switch(action.type) { case types.FETCH_NEW_TIME: return { ...state, currentTime: action.payload} default: return state; } } export default reducer
بعد از خارج کردن currentTime
از reducer ، باید فایل reducer.js
را بروزرسانی کنیم تا بتواند یک فایل جدید در rootReducer
دریافت کند. خوشبختانه اینکار خیلی راحت است.
import { combineReducers } from 'redux'; import * as currentUser from './currentUser'; import * as currentTime from './currentTime'; export const rootReducer = combineReducers({ currentTime: currentTime.reducer, currentUser: currentUser.reducer, }) export const initialState = { currentTime: currentTime.initialState, currentUser: currentUser.initialState, } export default rootReducer
در انتها، تابع configureStore
را برای خارج کردن rootReducer
و initialState
از فایل موردنظر، بروزرسانی می کنیم:
import { rootReducer, initialState } from './reducers' // ... export const configureStore = () => { const store = createStore( rootReducer, initialState, ); return store; }
middleware ها توابعی هستند که یک store را دریافت می کنند، و همان طور که انتظار می رود یک متدی که تابع next را دریافت کرده و یک تابع دیگر که یک اکشن را به عنوان پارامتر دریافت می کند، را به عنوان خروجی بر می گرداند. آیا سردرگم شدید؟
اجازه بدهید با یک مثال به طور عملی با این موضوع آشنا شویم.
در این قسمت می خواهیم یک middleware خیلی ساده ایجاد کنیم تا به طرز کار آنها آشنا شویم و ببینیم که چطور می شود آنها را به برنامه خود اضافه کنیم.
سینتکس یک middleware مانند زیر است:
const loggingMiddleware = (store) => (next) => (action) => { // Our middleware }
آیا چیزی از سینتکس آن نفهمیدید؟ نگران نباشید، این اولین باری است که این سینتکس را می بینیم. حال می خواهیم نگاه عمیق تری به آن بیندازیم و ببینیم که چطور کار می کند. ابتدا عبارت logginMiddleware
بالا را مطابق زیر بازنویسی می کنیم:
const loggingMiddleware = function(store) { // Called when calling applyMiddleware so // our middleware can have access to the store return function(next) { // next is the following action to be run // after this middleware return function(action) { // finally, this is where our logic lives for // our middleware. } } }
نیازی نیست نگران باشیم که این متدها چطور فراخوانی می شوند، چون به همان ترتیبی که نوشته، در نرم افزار اجرا خواهند شد. حال می خواهیم توسط logginMiddleware
اکشنی که فراخوانی شده را خارج کنیم:
const loggingMiddleware = (store) => (next) => (action) => { // Our middleware console.log(`Redux Log:`, action) // call the next function next(action); }
middleware باعث می شود که در Store هر زمانی که یک اکشن فراخوانی شده، جزئیات آن اکشن را توسط console.log
نمایش دهیم.
همچنین برای اعمال middleware به برنامه، از تابع applyMiddleware
که در آرگومان سوم متد ()createStore
قرار می گیرد، استفاده می کنیم.
import { createStore, applyMiddleware } from 'redux';
برای اعمال middleware همچنین می توانیم متد ()applyMiddleware
را در تابع ()createStore
فراخوانی کنیم. در فایل src/redux/configureStore.js
متد ()applyMiddleware
را به متد ()createStore
اضافه می کنیم.
const store = createStore( rootReducer, initialState, applyMiddleware( apiMiddleware, loggingMiddleware, ) );
حال middleware در محل مورد نظر قرار می گیرد. کنسول مرورگرتان را باز کرده و همان طور که می بینید تمام اکشن هایی که فراخوانی شده اند، نمایش داده می شوند. حال روی دکمه update
کلیک کنید تا خروجی را ببینید.
همان طور که دیدید، middleware ها به ما اجازه می دهند تا یک تابع را در زنجیره فراخوانی Redux action اضافه کنیم. داخل این توابع می توانیم در اکشن، به state دسترسی داشته باشیم و همچنین اکشن های دیگر را ارسال کنیم.
می توانیم یک middleware یی بنویسیم که تنها اکشن هایی متناظر با درخواست های api را دریافت کند.این middleware می تواند تنها اکشن هایی که یک ویژگی به خصوصی دارند را دریافت کنند. برای مثال می توانیم یک آبجکت meta
با نوعی برابر 'api'
روی اکشن داشته باشیم. از این قابلیت استفاده می کنیم تا مطمئن شویم middleware مان اکشن های دیگری که با درخواست های api مرتبط نیستند را مدیریت نکنند.
const apiMiddleware = store => next => action => { if (!action.meta || action.meta.type !== 'api') { return next(action); } // This is an api request }
اگر آبجکت meta
اکشنی از نوع 'api'
داشت، آن درخواست را توسط apiMiddleware
دریافت می کنیم.
حال updateTime()
، actionCreator را برای وارد کردن این خصوصیات به داخل یک درخواست api تغییر می دهیم.
ماژول currentTime که در مسیر src/redux/currentTIme.js
قرار دارد را باز کرده و تابع ()fetchNewTime
را پیدا کنید.
سپس url آن را به آبجکت meta برای این درخواست، ارسال می کنیم. حتی می توانیم داخل فراخوانی actionCreator
پارامتر هم دریافت کنیم.
const host = 'https://andthetimeis.com' export const fetchNewTime = ({ timezone = 'pst', str='now'}) => ({ type: types.FETCH_NEW_TIME, payload: new Date().toString(), meta: { type: 'api', url: host + '/' + timezone + '/' + str + '.json' } })
هنگامی که ما دکمه را برای بروزرسانی زمان فشار دهیم، apiMiddleware
مان این درخواست را قبل از اینکه به reducer برسد، دریافت می کند. برای هر فراخوانی که ما آن را در middleware دریافت می کنیم می توانیم بخش آبجکت meta
را جدا کرده و با استفاده از این گزینه ها یک درخواست ایجاد کنیم. همچنین می توانیم کل آبجکت meta را از طریق متد ()fetch
هم ارسال کنیم.
مراحلی که api middleware مان باید طی کند عبارتند از:
حال بیایید قدم به قدم جلو برویم. ابتدا url را گرفته و یک fetchOption
برای ارسال به ()fetch
ایجاد می کنیم. توضیحات ارائه شده تا به اینجا را در کد زیر نشان داده ایم:
const apiMiddleware = store => next => action => { if (!action.meta || action.meta.type !== 'api') { return next(action); } // This is an api request // Find the request URL and compose request options from meta const {url} = action.meta; const fetchOptions = Object.assign({}, action.meta); // Make the request fetch(url, fetchOptions) // convert the response to json .then(resp => resp.json()) .then(json => { // respond back to the user // by dispatching the original action without // the meta object let newAction = Object.assign({}, action, { payload: json.dateString }); delete newAction.meta; store.dispatch(newAction); }) } export default apiMiddleware
ما چندین گزینه برای نحوه ارسال پاسخ به کاربر در زنجیره Redux داریم.
ما ترجیح می دهیم با همان نوع پاسخ، بدون تگ meta پاسخ بدهیم و بدنه پاسخ را به عنوان payload اکشن جدید قرار دهیم.
به این ترتیب، ما مجبور نیستیم redux reducer را تغییر دهیم تا پاسخ را به گونه ای متفاوت مدیریت کنیم.
همچنین به یک پاسخ منفرد (تکی) هم محدود نیستیم. فرض کنید وقتی که درخواست کامل شد، کاربر یک کالبک onSuccess
را برای فراخوانی ارسال می کند.
ما می توانیم کالبک onSuccess
را فراخوانی کرده و سپس آن را به زنجیره ارسال کنیم:
const apiMiddleware = store => next => action => { if (!action.meta || action.meta.type !== 'api') { return next(action); } // This is an api request // Find the request URL and compose request options from meta const {url} = action.meta; const fetchOptions = Object.assign({}, action.meta); // Make the request fetch(url, fetchOptions) // convert the response to json .then(resp => resp.json()) .then(json => { if (typeof action.meta.onSuccess === 'function') { action.meta.onSuccess(json); } return json; // For the next promise in the chain }) .then(json => { // respond back to the user // by dispatching the original action without // the meta object let newAction = Object.assign({}, action, { payload: json.dateString }); delete newAction.meta; store.dispatch(newAction); }) }
حال با بروزرسانی کد زیر، api middleware را به زنجیره مان اضافه می کنیم.
import { createStore, applyMiddleware } from 'redux'; import { rootReducer, initialState } from './reducers' import loggingMiddleware from './loggingMiddleware'; import apiMiddleware from './apiMiddleware'; export const configureStore = () => { const store = createStore( rootReducer, initialState, applyMiddleware( apiMiddleware, loggingMiddleware, ) ); return store; } export default configureStore;
دقت داشته باشید که ما مجبور نیستیم که کدهای ویومان را برای بروزرسانی نحوه دستکاری داده در ساختار state تغییر بدهیم:
این ساده ترینmiddleware یی بود که نوشتیم، اما اصول کلی را به خوبی به شما نشان دادیم. آیا فکر کردید که چطور می شود یک سرویس Caching
(کش کردن) نوشت تا برای داده ای که قبلاً داشته ایم، درخواست جدید ارسال نکنیم؟چطور می شود درخواست های معلق را مدیریت کرد؟ برای مثال چطور می توان یک spinner برای درخواست هایی که هنوز کامل نشده اند، ایجاد کرد؟
بسیارخوب، تا به اینجا به تسلط خوبی بر Redux رسیدیم و می توانیم به مرحله بعدی برویم. در جلسات بعدی سعی می کنیم به این سوالات پاسخ دهیم.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.