對於react-thunk中介軟體的簡單理解
前言
剛來公司的時候,對react專案中的thunk中介軟體的作用一直不太瞭解,最近有時間決定好好研究一下。鑑於本人初次寫部落格,並已假設讀者已掌握redux的一些基本用法
;如有錯誤,還望指出。不勝感激!
首先簡單回顧一下redux工作流程
圖畫的不太好,見諒;
對於reactUI元件來說,資料的來源無外乎兩種,一種是使用者主動觸發的動作,例如點選事件、提交表單,輸入操作;另一種是元件主動的資料更新,如獲取頁面初始資料,子元件接受父元件的props變化而進行更新檢視操作;
如圖所示,無論那種對於資料的操作,對於view都會派發出一個action
狀態的更新
正如我們所知,在redux裡,每次更新後的Store都會對應著一個新的view,而Store裡面資料的更新依賴action的觸發————Store.dispatch(action)會自執行初始化中createStore中注入的reducers,從而計算出新的狀態。
import { createStore } from 'redux'
//reducer 計算狀態的純函式
//initialState 初始化資料
//enhancers中介軟體
createStore(reducers, initialState, enhancers)
action的使用和外掛的擴充套件
對於元件的輸入操作(如點選事件),可以將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>
)
或者通過connect方法,從元件的props中拿到dispatch方法,發出一個action
// 將state注入到元件的props裡
// 注意,這裡的state指的是redux管理的資料,每一個view的狀態對應著
// 唯一的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>`
)
那麼問題來了,dispatch是同步執行reducers生成新狀態的,對於頁面的操作沒有問題;但是如果點選事件是請求了某個結果,需要等待結果響應後再更新檢視呢?應該如何處理?
因而redux引入了thunk中介軟體,對action進行了擴充套件
##thunk中介軟體解決了什麼問題?
引入thunk外掛後,我們可以在actionCreators內部編寫邏輯,處理請求結果。而不只是單純的返回一個action物件。
//未引入前的寫法
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其實可以接受三個引數,第二個引數preloadedState一般作為整個應用的初始化資料,如果傳入了這個引數,applyMiddleware就會被當做第三個引數處理
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk)
);
中介軟體都要放到applyMiddleware裡,如果要新增中介軟體,可以依次新增,但是要遵循文件定義的順序
const store = createStore(
reducer,
initialState,
applyMiddleware(thunk,middleware1, middleware2)
);
原始碼解讀
也許你會奇怪,為什麼使用的時候要按照上面的寫法,那我們就一起看下方法的實現
首先是createStore的引數順序
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.');
}
第一個判斷已經告訴了我們答案,引數的型別檢驗結果決定了順序
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顧名思義,用於呼叫各種中介軟體;
2、applyMiddleware執行後,將所有入參中介軟體存入一個數組,並且返回一個閉包(閉包的概念不做累述)
3、閉包接受一個createStore作為入參並且執行後返回下一個閉包,createStore這個入參有沒有很眼熟,沒錯,就是redux的createStore。
返回結果
返回將所有中介軟體串聯存入的dispatch,執行時從右向左執行,第一次的執行結果會返回給一下個,依次類推。
如何實現每個中介軟體串聯執行
_dispatch = compose.apply(undefined, chain),使用了一個compose函式,呼叫之後就可以將所有中介軟體串聯起來,那麼compose又是如何實現的呢?
精華所在
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));
};
});
}
個人認為這個compose函式是整個redux中非常亮眼的部分,短短几行程式碼,就完成了一個核心功能的擴充套件,是責任鏈設計模式的經典體現。
ALSO 我們也可以使用這個compose方法對applyMiddleware進行擴充套件
let devtools = () => noop => {
console.log(noop);
return noop; //createStore
};
const enhancers = [
applyMiddleware(...middleware),
devtools()
];
createStore(reducers, initialState, compose(...enhancers));
然後回來,我們就明白了createStore中的設計
//如果存在中介軟體引數,那麼將會得到一個經過改裝的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經過了怎樣的改裝
如上已經說過,compose會將傳入的函式陣列從右向左串聯執行
compose.apply(undefined, chain)(store.dispatch);
thunk一定會接受上一個中介軟體的執行結果繼續執行,然後最終在createState裡返回一個改造好的dispatch, 接下來我只要看下thunk是怎樣實現的,就瞭解了整個中介軟體使用的原理:
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
//最終的dispatch
//next就是接收的store.dispatch引數,為上一個中介軟體改造過的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入參接受的資料型別:
1、非function,不處理,將action 傳給下一個中介軟體,最終都會根據傳入的action計算相應的reducers(開頭說的自執行)————store.dispatch(action)
2、function型別的action, 自動觸發函式,並且將store.dispatch傳入
總結
再結合開始介紹的thunk用法,我們就明白了thunk的原理,可以在actionCreators裡通過返回一個函式,然後就可以在函式裡編寫某些非同步操作了,待非同步操作結束,最後通過傳入的store.dispatch,發出action通知給Store要進行狀態更新。