用過 Express 或 Koa 相似框架的同窗可能知道,在 Express 中,中間件(middleware)就是在 req 進來以後,在咱們真正對 req 進行處理以前,咱們先對 req 進行必定的預處理,而這個預處理的過程就由 middleware 來完成。
同理,在 Redux 中,middleware 就是擴展了在 dispatch action 以後,到 action 到達 reducer 以前之間的中間這段時間,而中間的這段時間就是 dispatch 的過程,因此 Redux 的 middleware 的原理就是改造 dispatch。javascript
讓咱們先從一個最簡單的日誌 middleware 定義開始:html
const logger = store => next => action => { console.group('logger'); console.warn('dispatching', action); let result = next(action); console.warn('next state', store.getState()); console.groupEnd(); return result; };
這個 logger
函數就是一個 Redux 中的 middleware ,它的功能是在 store.dispatch(action)
(對應 middleware 中的 next(action)
) 以前和以後分別打印出一條日誌。從咱們的 logger
中能夠看到,咱們向 middleware 中傳入了 store
,以便咱們在 middleware 中獲取使用 store.getState()
獲取 state,咱們還在以後的函數中傳入了 next
,而最後傳入的 action
就是咱們平時 store.dispatch(action)
中的 action,因此 next(action)
對應的就是 dispatch(action)
。java
最後咱們還須要調用並 next(action)
來執行本來的 dispatch(action)
。git
最後咱們能夠在使用 createStore()
建立 store 的時候,把這個 middleware 加入進去,使得每次 store.dispathc(action)
的時候都會打印出日誌:github
import { createStore, applyMiddleware } from 'redux'; // 導入 applyMiddleware const store = createStore(counter, applyMiddleware(logger));
注意,這裏咱們使用了 Redux 提供的 applyMiddleware()
來在建立 store 的時候應用 middleware,而 applyMiddleware()
返回的是一個應用了 middleware 的 store enhancer,也就是一個加強型的 store。編程
createStore()
接受三個參數,第一個是 reducer,第二個若是是對象,那麼就被做爲 store 的初始狀態,第三個就是 store enhancer,若是第二個參數是函數,那麼就被看成 store enhancer。json
關於 applyMiddleware
和咱們自定義的 logger
是如何一塊兒工做的,這個咱們稍後再講。redux
爲了說明後一條日誌 console.warn('next state', store.getState())
是在執行了 reducer 以後打印出來的,咱們在 reducer 中也打印一個消息。改造後的 reducer:api
function counter(state = 0, action) { + console.log('hi,這條 log 從 reducer 中來'); switch(action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default : return state; } }
這裏,我使用了 #1 中的計數器做爲例子。服務器
能夠看到,在 reducer 中打印的消息處於 middleware 日誌的中間,這是由於在 logger
middleware 中,將 let result = next(action);
寫在了最後一條消息的前面,一旦調用了 next(action)
,就會進入 reducer 或者進入下一個 middleware(若是有的話)。相似 Koa 中間件的洋蔥模型。
其實 next(action)
就至關於 store.dispatch(action)
,意思是開始處理下一個 middleware,若是沒有 middleware 了就使用原始 Redux 的 store.dispatch(action)
來分發動做。這個是由 Redux 的 applyMiddleware
來處理的,那麼 applyMiddleware()
是如何實現對 middleware 的處理的呢?稍後咱們會對它進行簡單的講解 。
從 applyMiddleware 的設計思路 中,咱們能夠看到 Redux 中的 store 只是包含一些方法(dispatch()
、subscribe()
、getState()
、replaceReducer()
)的對象。咱們可使用
const next = store.dispatch;
來先引用原始 store 中的 dispatch 方法,而後等到合適的時機,咱們再調用它,實現對 dispatch 方法的改造。
Middleware 接收一個名爲next
的 dispatch 函數(只是dispatch
函數的引用),並返回一個改造後的 dispatch 函數,而返回的 dispatch 函數又會被做爲下一個 middleware 的next
,以此類推。因此,一個 middleware 看起來就會相似這樣:
function logger(next) { return action => { console.log('在這裏中一些額外的工做') return next(action) } }
其中,在 middleware 中返回的 dispatch 函數接受一個 action
做爲參數(和普通的 dispatch 函數同樣),最後再調用 next 函數並返回,以便下一個 middleware 繼續,若是沒有 middleware 則 直接返回。
因爲 store 中相似getState()
的方法依舊很是有用,咱們將store
做爲頂層的參數,使得它能夠在全部 middleware 中被使用。這樣的話,一個 middleware 的 API 最終看起來就變成這樣:
function logger(store) { return next => { return action => { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } } }
值得一提的是,Redux 中使用到了許多函數式編程的思想,若是你對
比較陌生的話,建議你先去補充如下函數式編程思想的內容。applyMiddleware 的源碼
store.dispatch(action)
來更新新的 UI;經過仿照 redux-thunk,咱們也能夠本身寫一個支持異步 action 的 middleware,以下:
const myThunkMiddleware = store => next => action => { if (typeof action === 'function') { // 若是 action 是函數,通常的 action 爲純對象 return action(store.dispatch, store.getState); // 調用 action 函數 } return next(action); };
異步 action creator :
export function fetchGithubUser(username = 'bbbbx') { return dispatch => { // 先 dispatch 一個同步 action dispatch({ type: 'INCREMENT', text: '加載中...' }); // 異步 fetch Github API fetch(`https://api.github.com/search/users?q=${username}`) .then(response => response.json()) .then(responseJSON => { // 異步請求返回後,再 dispatch 一個 action dispatch({ type: 'INCREMENT', text: responseJSON }); }); }; }
修改 reducer,使它能夠處理 action 中的 action.text
:
function counter(state = { value: 0, text: '' }, action) { switch(action.type) { case 'INCREMENT': return { value: state.value + 1, text: action.text }; case 'DECREMENT': return { value: state.value - 1, text: action.text }; default : return state; } }
再改造一下 Counter 組件,展現 Github 用戶:
// Counter.js class Counter extends React.Component { constructor(props) { super(props); this.state = { username: '' }; } handleChange(event) { this.setState({ username: event.target.value }); } handleSearch(event) { event.preventDefault(); if (this.state.username === '') { return ; } this.props.fetchGithubUser(this.state.username); } render() { const { text, value, increment, decrement } = this.props; let users = text; if (text.items instanceof Array) { if (text.items.length === 0) { users = '用戶不存在!'; } else { users = text.items.map(item => ( <li key={item.id}> <p>用戶名:<a href={item.html_url}>{item.login}</a></p> <img width={100} src={item.avatar_url} alt='item.avatar_url' /> </li> )); } } return ( <div> Click: {value} times {' '} <button onClick={increment} >+</button>{' '} <button onClick={decrement} >-</button>{' '} <div> <input type='text' onChange={this.handleChange.bind(this)} /> <button onClick={this.handleSearch.bind(this)} >獲取 Github 用戶</button>{' '} </div> <br /> <b>state.text:{users}</b> </div> ); } }
利用 redux-thunk ,咱們能夠完成各類複雜的異步 action,儘管 redux-thunk 這個 middleware 只有 數十行 代碼。先導入 redux-thunk:
import thunkMiddleware from 'redux-thunk'; const store = createStore( counter, applyMiddleware(thunkMiddleware) );
以後即可定義異步的 action creator 了:
export function incrementAsync(delay = 1000) { return dispatch => { dispatch(decrement()); setTimeout(() => { dispatch(increment()); }, delay); }; }
使用:
<button onClick={increment} >+</button>{' '} <button onClick={decrement} >-</button>{' '} + <button onClick={() => incrementAsync(1000) } >先 - 1 ,後再 + 1</button>{' '}
注意,異步 action creator 要寫成 onClick={() => incrementAsync(1000) }
匿名函數調用的形式。