在react中,中間件就是一個函數,對store.dispatch方法進行了改造,在發出Action和執行Reducer這兩個步驟之間,增加了其他功能;常用的中間件都有現成的,只要引用別人寫好的模組即可。
為了理解中間件,讓我們站在框架作者的角度思考問題:如果要新增功能,你會在哪個環節新增?
(1)Reducer:純函數,只承擔計算 State 的功能,不合適承擔其他功能,也承擔不了,因為理論上,純函數不能進行讀寫操作。
(2)View:與 State 一一對應,可以看作 State 的視覺層,也不合適承擔其他功能。
(3)Action:存放資料的對象,也就是訊息的載體,只能被別人操作,自己不能做任何動作。
想來想去,只有發送 Action 的這個步驟,也就是store.dispatch()方法,可以加入功能。舉例來說,要新增日誌功能,把 Action 和 State 印出來,可以將store.dispatch如下改造。
let next = store.dispatch; store.dispatch = function dispatchAndLog(action) { console.log('dispatching', action); next(action); console.log('next state', store.getState()); }
上面程式碼中,對store.dispatch進行了重定義,在發送 Action 前後新增了列印功能。這就是中間件的雛型。
中間件就是一個函數,對store.dispatch方法進行了改造,在發出 Action 和執行 Reducer 這兩步驟之間,增加了其他功能。常用的中間件都有現成的,只要引用別人寫好的模組即可。
本教學不涉及如何寫中間件,因為常用的中間件都有現成的,只要引用別人寫好的模組即可。例如,上一節的日誌中間件,就有現成的redux-logger模組。這裡只介紹怎麼使用中間件。
import { applyMiddleware, createStore } from 'redux'; import createLogger from 'redux-logger'; const logger = createLogger(); const store = createStore( reducer, applyMiddleware(logger) );
上面程式碼中,redux-logger提供一個生成器createLogger,可以產生日誌中間件logger。然後,將它放在applyMiddleware方法之中,傳入createStore方法,就完成了store.dispatch()的功能增強。
這裡有兩點要注意:
(1)createStore方法可以接受整個應用的初始狀態當參數,那樣的話,applyMiddleware就是第三個參數了。
const store = createStore( reducer, initial_state, applyMiddleware(logger) );
(2)中間件的次序有講究。
const store = createStore( reducer, applyMiddleware(thunk, promise, logger) );
上面程式碼中,applyMiddleware方法的三個參數,就是三個中間件。有的中間件有次序要求,使用前要查一下文件。例如,logger就一定要放在最後,否則輸出結果會不正確。
看到這裡,你可能會問,applyMiddlewares這個方法到底是做什麼的?
它是 Redux 的原生方法,作用是將所有中間件組成一個數組,依序執行。下面是它的原始碼。
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain)(store.dispatch); return {...store, dispatch} } }
上面程式碼中,所有中間件都放進了一個陣列chain,然後巢狀執行,最後執行store.dispatch。可以看到,中介軟體內部(middlewareAPI)可以拿到getState和dispatch這兩個方法。
理解了中間件以後,就可以處理非同步運算了。
同步運算只要發出一種 Action 即可,非同步作業的差異就是它要發出三種 Action。
操作發起時的Action
在操作成功時的Action
操作失敗時的Action
以向伺服器取出資料為例,三種Action 可以有兩種不同的寫入方法。
// 写法一:名称相同,参数不同 { type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: { ... } } // 写法二:名称不同 { type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Oops' } { type: 'FETCH_POSTS_SUCCESS', response: { ... } }
除了 Action 種類不同,非同步操作的 State 也要進行改造,反映不同的操作狀態。下面是 State 的一個例子。
let state = { // ... isFetching: true, didInvalidate: true, lastUpdated: 'xxxxxxx' };
上面程式碼中,State 的屬性isFetching表示是否在抓取資料。 didInvalidate表示資料是否過時,lastUpdated表示上一次更新時間。
現在,整個非同步操作的思路就很清楚了。
作業開始時,送一個Action,觸發State 更新為"正在操作"狀態,View 重新渲染
作業結束後,再送出一個Action,觸發State 更新為"操作結束"狀態,View 再一次重新渲染
class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props dispatch(fetchPosts(selectedPost)) } // ...###上面程式碼是一個非同步元件的例子。載入成功後(componentDidMount方法),它送出了(dispatch方法)一個 Action,向伺服器要求資料 fetchPosts(selectedSubreddit)。這裡的fetchPosts就是 Action Creator。 ######下面就是fetchPosts的程式碼,關鍵之處就在裡面。 ###
const fetchPosts = postTitle => (dispatch, getState) => { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) .then(json => dispatch(receivePosts(postTitle, json))); }; }; // 使用方法一 store.dispatch(fetchPosts('reactjs')); // 使用方法二 store.dispatch(fetchPosts('reactjs')).then(() => console.log(store.getState()) );
上面代码中,fetchPosts是一个Action Creator(动作生成器),返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json))。
上面代码中,有几个地方需要注意。
(1)fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象。
(2)返回的函数的参数是dispatch和getState这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容。
(3)在返回的函数之中,先发出一个 Action(requestPosts(postTitle)),表示操作开始。
(4)异步操作结束之后,再发出一个 Action(receivePosts(postTitle, json)),表示操作结束。
这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数。
这时,就要使用中间件redux-thunk。
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import reducer from './reducers'; // Note: this API requires redux@>=3.1.0 const store = createStore( reducer, applyMiddleware(thunk) );
上面代码使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。
因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch。
既然 Action Creator 可以返回函数,当然也可以返回其他值。另一种异步操作的解决方案,就是让 Action Creator 返回一个 Promise 对象。
这就需要使用redux-promise中间件。
import { createStore, applyMiddleware } from 'redux'; import promiseMiddleware from 'redux-promise'; import reducer from './reducers'; const store = createStore( reducer, applyMiddleware(promiseMiddleware) );
这个中间件使得store.dispatch方法可以接受 Promise 对象作为参数。这时,Action Creator 有两种写法。写法一,返回值是一个 Promise 对象。
const fetchPosts = (dispatch, postTitle) => new Promise(function (resolve, reject) { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => { type: 'FETCH_POSTS', payload: response.json() }); });
写法二,Action 对象的payload属性是一个 Promise 对象。这需要从redux-actions模块引入createAction方法,并且写法也要变成下面这样。
import { createAction } from 'redux-actions'; class AsyncApp extends Component { componentDidMount() { const { dispatch, selectedPost } = this.props // 发出同步 Action dispatch(requestPosts(selectedPost)); // 发出异步 Action dispatch(createAction( 'FETCH_POSTS', fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) )); }
上面代码中,第二个dispatch方法发出的是异步 Action,只有等到操作结束,这个 Action 才会实际发出。注意,createAction的第二个参数必须是一个 Promise 对象。
看一下redux-promise的源码,就会明白它内部是怎么操作的。
export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; }
从上面代码可以看出,如果 Action 本身是一个 Promise,它 resolve 以后的值应该是一个 Action 对象,会被dispatch方法送出(action.then(dispatch)),但 reject 以后不会有任何动作;如果 Action 对象的payload属性是一个 Promise 对象,那么无论 resolve 和 reject,dispatch方法都会发出 Action。
以上是react中間件是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!