淺談對(duì)于react-thunk中間件的簡(jiǎn)單理解
前言
剛來(lái)公司的時(shí)候,對(duì)react項(xiàng)目中的thunk中間件的作用一直不太了解,最近有時(shí)間決定好好研究一下。鑒于本人初次寫(xiě)博客,并已假設(shè)讀者已掌握redux的一些基本用法;如有錯(cuò)誤,還望指出。不勝感激!
首先簡(jiǎn)單回顧一下redux工作流程

圖畫(huà)的不太好,見(jiàn)諒;
對(duì)于reactUI組件來(lái)說(shuō),數(shù)據(jù)的來(lái)源無(wú)外乎兩種,一種是用戶主動(dòng)觸發(fā)的動(dòng)作,例如點(diǎn)擊事件、提交表單,輸入操作;另一種是組件主動(dòng)的數(shù)據(jù)更新,如獲取頁(yè)面初始數(shù)據(jù),子組件接受父組件的props變化而進(jìn)行更新視圖操作;
如圖所示,無(wú)論那種對(duì)于數(shù)據(jù)的操作,對(duì)于view都會(huì)派發(fā)出一個(gè)action
狀態(tài)的更新
正如我們所知,在redux里,每次更新后的Store都會(huì)對(duì)應(yīng)著一個(gè)新的view,而Store里面數(shù)據(jù)的更新依賴action的觸發(fā)————Store.dispatch(action)會(huì)自執(zhí)行初始化中createStore中注入的reducers,從而計(jì)算出新的狀態(tài)。
import { createStore } from 'redux'
//reducer 計(jì)算狀態(tài)的純函數(shù)
//initialState 初始化數(shù)據(jù)
//enhancers中間件
createStore(reducers, initialState, enhancers)
action的使用和插件的擴(kuò)展
對(duì)于組件的輸入操作(如點(diǎn)擊事件),可以將store.dispatch(action)綁定到組件
const store = createStore(reducer);
const TodoList = ({ state, someActionCreator }) => (
<ul>
{state.map(someState =>
<Todo
key={someState.someData}
onClick={() => store.dispatch(someActionCreator(state.someData))}
/>
</ul>
)
或者通過(guò)connect方法,從組件的props中拿到dispatch方法,發(fā)出一個(gè)action
// 將state注入到組件的props里
// 注意,這里的state指的是redux管理的數(shù)據(jù),每一個(gè)view的狀態(tài)對(duì)應(yīng)著
// 唯一的state;
// state的集合就是redux管理的store
const mapStateToProps = store => ({
state: store.state
})
// 將action注入到組件的props 里
const mapDispatchToProps = dispatch => ({
actions: state => dispatch(actionCreators(state))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
然后組件綁定事件就可以改成這樣 ,( actionCreators用于生成action, 參考官方鏈接 https://redux.js.org/basics/actions)
const TodoList = ({ state, actions }) => (
`<ul>
{state.map(someState =>
<Todo
key={someState.someData}
onClick={() => actions(someState.someData)}
/>
</ul>`
)
那么問(wèn)題來(lái)了,dispatch是同步執(zhí)行reducers生成新?tīng)顟B(tài)的,對(duì)于頁(yè)面的操作沒(méi)有問(wèn)題;但是如果點(diǎn)擊事件是請(qǐng)求了某個(gè)結(jié)果,需要等待結(jié)果響應(yīng)后再更新視圖呢?應(yīng)該如何處理?
因而redux引入了thunk中間件,對(duì)action進(jìn)行了擴(kuò)展
##thunk中間件解決了什么問(wèn)題?
引入thunk插件后,我們可以在actionCreators內(nèi)部編寫(xiě)邏輯,處理請(qǐng)求結(jié)果。而不只是單純的返回一個(gè)action對(duì)象。
//未引入前的寫(xiě)法
let nextTodoId = 0
export const addTodo = text => ({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
//引入thunk后
let nextTodoId = 0
export const addTodo = text => ({
return async dispatch => {
//dosomething, request
await request()
dispatch({
type: 'ADD_TODO',
id: nextTodoId++,
text
})
}
})
thunk中間件的使用方法
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
const store = createStore(
reducer,
applyMiddleware(thunk)
);
createStore其實(shí)可以接受三個(gè)參數(shù),第二個(gè)參數(shù)preloadedState一般作為整個(gè)應(yīng)用的初始化數(shù)據(jù),如果傳入了這個(gè)參數(shù),applyMiddleware就會(huì)被當(dāng)做第三個(gè)參數(shù)處理
const store = createStore( reducer, initialState, applyMiddleware(thunk) );
中間件都要放到applyMiddleware里,如果要添加中間件,可以依次添加,但是要遵循文檔定義的順序
const store = createStore( reducer, initialState, applyMiddleware(thunk,middleware1, middleware2) );
源碼解讀
也許你會(huì)奇怪,為什么使用的時(shí)候要按照上面的寫(xiě)法,那我們就一起看下方法的實(shí)現(xiàn)
首先是createStore的參數(shù)順序
function createStore(reducer, preloadedState, enhancer) {
var _ref2;
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState;
preloadedState = undefined;
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.');
}
第一個(gè)判斷已經(jīng)告訴了我們答案,參數(shù)的類(lèi)型檢驗(yàn)結(jié)果決定了順序
applyMiddleware是干什么用的
function applyMiddleware() {
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function () {
for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var store = createStore.apply(undefined, args);
var _dispatch = function dispatch() {
throw new Error('Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.');
};
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch() {
return _dispatch.apply(undefined, arguments);
}
};
var chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
_dispatch = compose.apply(undefined, chain)(store.dispatch);
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
代碼不多,而且非常清晰:
1、applyMiddleware顧名思義,用于調(diào)用各種中間件;
2、applyMiddleware執(zhí)行后,將所有入?yún)⒅虚g件存入一個(gè)數(shù)組,并且返回一個(gè)閉包(閉包的概念不做累述)
3、閉包接受一個(gè)createStore作為入?yún)⒉⑶覉?zhí)行后返回下一個(gè)閉包,createStore這個(gè)入?yún)⒂袥](méi)有很眼熟,沒(méi)錯(cuò),就是redux的createStore。
返回結(jié)果
返回將所有中間件串聯(lián)存入的dispatch,執(zhí)行時(shí)從右向左執(zhí)行,第一次的執(zhí)行結(jié)果會(huì)返回給一下個(gè),依次類(lèi)推。
如何實(shí)現(xiàn)每個(gè)中間件串聯(lián)執(zhí)行
_dispatch = compose.apply(undefined, chain),使用了一個(gè)compose函數(shù),調(diào)用之后就可以將所有中間件串聯(lián)起來(lái),那么compose又是如何實(shí)現(xiàn)的呢?
精華所在
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}
個(gè)人認(rèn)為這個(gè)compose函數(shù)是整個(gè)redux中非常亮眼的部分,短短幾行代碼,就完成了一個(gè)核心功能的擴(kuò)展,是責(zé)任鏈設(shè)計(jì)模式的經(jīng)典體現(xiàn)。
ALSO 我們也可以使用這個(gè)compose方法對(duì)applyMiddleware進(jìn)行擴(kuò)展
let devtools = () => noop => {
console.log(noop);
return noop; //createStore
};
const enhancers = [
applyMiddleware(...middleware),
devtools()
];
createStore(reducers, initialState, compose(...enhancers));
然后回來(lái),我們就明白了createStore中的設(shè)計(jì)
//如果存在中間件參數(shù),那么將會(huì)得到一個(gè)經(jīng)過(guò)改裝的dispatch
// return _extends({}, store, {dispatch: _dispatch});
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.');
}
return enhancer(createStore)(reducer, preloadedState);
}
dispatch經(jīng)過(guò)了怎樣的改裝
如上已經(jīng)說(shuō)過(guò),compose會(huì)將傳入的函數(shù)數(shù)組從右向左串聯(lián)執(zhí)行
compose.apply(undefined, chain)(store.dispatch);
thunk一定會(huì)接受上一個(gè)中間件的執(zhí)行結(jié)果繼續(xù)執(zhí)行,然后最終在createState里返回一個(gè)改造好的dispatch, 接下來(lái)我只要看下thunk是怎樣實(shí)現(xiàn)的,就了解了整個(gè)中間件使用的原理:
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
//最終的dispatch
//next就是接收的store.dispatch參數(shù),為上一個(gè)中間件改造過(guò)的dispatch
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
};
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
代碼同樣精煉,改造后的dispatch入?yún)⒔邮艿臄?shù)據(jù)類(lèi)型:
1、非function,不處理,將action 傳給下一個(gè)中間件,最終都會(huì)根據(jù)傳入的action計(jì)算相應(yīng)的reducers(開(kāi)頭說(shuō)的自執(zhí)行)————store.dispatch(action)
2、function類(lèi)型的action, 自動(dòng)觸發(fā)函數(shù),并且將store.dispatch傳入
總結(jié)
再結(jié)合開(kāi)始介紹的thunk用法,我們就明白了thunk的原理,可以在actionCreators里通過(guò)返回一個(gè)函數(shù),然后就可以在函數(shù)里編寫(xiě)某些異步操作了,待異步操作結(jié)束,最后通過(guò)傳入的store.dispatch,發(fā)出action通知給Store要進(jìn)行狀態(tài)更新。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
react 路由跳轉(zhuǎn)的7種方式實(shí)現(xiàn)
本文介紹了React中六種常見(jiàn)的路由跳轉(zhuǎn)方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-03-03
在React頁(yè)面重新加載時(shí)保留數(shù)據(jù)的實(shí)現(xiàn)方法總結(jié)
在React頁(yè)面重新加載時(shí)保留數(shù)據(jù),可以通過(guò)多種方法來(lái)實(shí)現(xiàn),常見(jiàn)的方法包括使用瀏覽器的本地存儲(chǔ)(Local Storage 或 Session Storage)、URL參數(shù)、以及服務(wù)器端存儲(chǔ)等,本文給大家總結(jié)了一些具體實(shí)現(xiàn)方法,需要的朋友可以參考下2024-06-06
React Native自定義標(biāo)題欄組件的實(shí)現(xiàn)方法
今天講一下如何實(shí)現(xiàn)自定義標(biāo)題欄組件,我們都知道RN有一個(gè)優(yōu)點(diǎn)就是可以組件化,在需要使用該組件的地方直接引用并傳遞一些參數(shù)就可以了,這種方式確實(shí)提高了開(kāi)發(fā)效率。對(duì)React Native自定義標(biāo)題欄組件的實(shí)現(xiàn)方法感興趣的朋友參考下2017-01-01
詳解在React項(xiàng)目中安裝并使用Less(用法總結(jié))
這篇文章主要介紹了詳解在React項(xiàng)目中安裝并使用Less(用法總結(jié)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
React.memo函數(shù)中的參數(shù)示例詳解
這篇文章主要為大家介紹了React.memo函數(shù)中的參數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
React Native 資源包拆分實(shí)戰(zhàn)分析
這篇文章主要為大家介紹了React Native 資源包拆分實(shí)戰(zhàn)分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

