【React全家桶入門之十三】Redux中間件與異步action

在上一篇中咱們瞭解到,更新Redux中狀態的流程是這種:action -> reducer -> new state。javascript

文中也講到。action是一個普通的javascript對象、reducer是一個普通的方法。在reducer中依據當前的state、接收到的action來生成一個新的state以達到更新狀態的目的。html

那麼問題來了。每次action被觸發(dispatch)。reducer就會同步地對store進行更新,在實際開發項目的時候,有很是多需求都是需要經過接口等形式獲取異步數據後再進行更新操做的。怎樣異步地對store進行更新呢?java

仍是拿上一篇文中寫的計數器來作樣例,現在需要加入一個按鈕。點擊這個按鈕的時候會在1秒以後對counter的值進行加1操做。react

最簡單的方法固然是把store.dispatch({type: ‘INCREASE’})放到一個setTimeout的回調裏去運行:git

document.getElementById('btn_async_increase').addEventListener('click', function () {
  setTimeout(function () {
    store.dispatch({type: 'INCREASE'});
  }, 1000);
});

但是這事實上仍是一次同步的更新操做,並不是咱們想要的。(在dispatch以後的store更新仍是同步的)github

使用中間件

Redux自己提供的Api與功能並很少,但是它提供了一箇中間件(插件)機制,可以使用第三方提供的中間件或本身編寫一箇中間件來對Redux的功能進行加強,比方可以使用redux-logger這個中間件來記錄action以及每次action先後的state、使用redux-undo來取消/重作action、使用redux-persist-store來對store進行持久化等等。npm

不少其它的中間件及相關工具可以查看這裏編程

要實現異步的action。有多個現成的中間件可供選擇,這裏選擇官方文檔中使用的redux-thunk這個中間件來實現。json

安裝ReduxThunk

首先固然需要先獲取redux-thunk,在通常的項目裏使用npm i redux-thunk -S來安裝到項目中,在codepen裏僅僅需簡單地設置一下外部js文件引入就可以了。redux

使用applyMiddleware掛載中間件

在建立store的時候。咱們將ReduxThunk使用Redux.applyMiddleware方法進行包裝後傳給Redux.createStore的第二個方法:

const {createStore, applyMiddleware} = Redux;

const store = createStore(counter, applyMiddleware(ReduxThunk.default));

異步action

原來的store.dispatch方法僅僅能接收一個普通的action對象做爲參數。當咱們加入了ReduxThunk這個中間件以後。store.dispatch還可以接收一個方法做爲參數,這種方法會接收到兩個參數,第一個是dispatch。等同於store.dispatch,第二個是getState,等同於store.getState,也就是說。現在可以這樣來觸發INCREASE:

store.dispatch((dispatch, getState) => dispatch({type: 'INCREASE'}));

點擊按鈕一秒後運行dispatch:

<div>
  <p>Count: <span id="value">0</span></p>
  <button id="btn_increase">+ 1</button>
  <button id="btn_async_increase">+ 1 async</button>
  <button id="btn_decrease">- 1</button>
</div>
document.getElementById('btn_async_increase').addEventListener('click', function () {
  store.dispatch((dispatch, getState) => {
    setTimeout(() => {
      dispatch({type: 'INCREASE'});
    }, 1000);
  });
});

看一下效果(CodePen):

ReduxThunk解析

依託於ReduxThunk咱們實現了異步觸發action的功能,那麼ReduxThunk是怎麼作到的呢?

咱們來看看ReduxThunk的代碼。ReduxThunk一共僅僅有一個代碼文件,14行代碼:

https://github.com/gaearon/redux-thunk/blob/master/src/index.js

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

咱們使用的是默認導出的thunk,也就是createThunkMiddleware()運行的結果。

createThunkMiddleware方法裏返回了一個奇怪的東西:

({ dispatch, getState }) => next => action => {
  ...
};

換一種寫法:

function ({dispatch, getState}) {
  return function (next) {
    return function (action) {
      ...
    };
  };
}

事實上就是一個多層嵌套返回函數的函數,使用箭頭的寫法在函數式編程中叫作柯里化。對柯里化更具體的介紹可以看一看這篇張鑫旭的博客

第一個(最外層)方法的參數是一個包括dispatch和getState字段(方法)的對象。事實上就是store對象。因此也可以寫成:

store => next => action => {
  ...
};

這事實上就是一個Redux中間件的基本寫法

參數next是一個方法,這種方法的做用是通知下一個Redux中間件對此次的action進行處理: next(action),假設一箇中間件中沒有運行next(action),則action會中止向興許的中間件傳遞。並阻止reducer的運行(store將不會因爲本次的action而更新)。

參數action就不用多說了,就是當前被觸發的action。

在ReduxThunk這個中間件中,作的處理很是easy:推斷當前的action是否爲一個方法。假設是,就運行action這種方法,並將store.dispatchstore.getState方法做爲參數傳遞給action方法;假設不是,則運行next(action)將控制權轉移給下一個中間件(假設有)。

因此當咱們給store.dispatch方法傳入一個方法的時候。ReduxThunk就會去運行這種方法,以達到自由控制action觸發流程的一個目的。

ReudxThunk在實際項目中的應用

上面咱們給計數器寫的異步action僅僅是爲了做演示,簡單地使用setTimeout來觸發action。如下給出一個比較貼近現實的樣例。

在某個項目中。咱們需要依據一個userId來調用後端接口獲取這個用戶的具體信息並存儲到Redux store中:

function getUserDetail (userId) {
  return (dispatch, getState) => {
    if (getState().user.id === userId) {
      // store中的user已經爲當前的目標user,無需反覆獲取
      return;
    }

    dispatch({type: 'USER_DETAIL_REQUEST', payload: userId});
    fetch(`${API_ROOT}/user/${userId}`)
      .then(res => res.json())
      .then(res => {
        // 觸發SUCCESS的action後在reducer中更新user數據
        dispatch({type: 'USER_DETAIL_REQUEST_SUCCESS', payload: res});
      })
      .catch(err => dispatch({type: 'USER_DETAIL_REQUST_FAILURE', payload: err})
  };
}

// 獲取userId爲10000的用戶詳情
store.dispatch(getUserDetail(10000));
相關文章
相關標籤/搜索