Redux中間件與異步Action

在以前的淺談Flux架構及Redux實踐一文中咱們初步的談及了Redux的數據流思想,並作了一個簡單的加減器。可是尚未接觸到Redux更多經常使用的場景,異步操做、API調用,如何鏈接到UI層等,Redux能夠與不少框架搭配包括Vue、React甚至是純JavaScript。後面咱們會用一個實例--經過github API獲取我的信息,來將Redux middleware、async action、鏈接到React貫穿其中。先看看咱們最後寫的demo的樣子。javascript

clipboard.png

Middleware與異步Action

依然先看看Redux做者Dam的描述:css

It provides a third-party extension point between dispatching an
action, and the moment it reaches the reducer.

個人理解是,middleware提供了一個你能夠修改action的機制,這和Express/Koa的中間件有些相似,只不過這裏的中間件主要是操做action。中間件對異步的action實現很是重要,由於在以前的文章中咱們談到,action是一個行爲抽象,只是一個對象,reducer是一個純函數,不該該有API調用和反作用的操做。那麼怎麼解決異步的問題?咱們確定不能在reducer中寫,那麼就考慮到了action -> reducer這個過程,這就是redux middleware:html

action -> middleware modify action -> reducer

它提供的是位於 action 被髮起以後,到達 reducer 以前的擴展點。 你能夠利用 Redux middleware 來進行日誌記錄、建立崩潰報告、調用異步接口或者路由等等。java

在上一篇文章中咱們使用的同步action,action creator返回的是一個對象,可是異步action能夠是一個函數,雖然函數也是對象,這裏咱們只是爲了區分兩種不一樣的狀況。經過使用指定的 middleware,action creator能夠返回函數。這時,這個 action creator 就成爲了 thunk。當 action creator 返回函數時,這個函數會被 Redux Thunk middleware 執行。這個函數並不須要保持純淨,它還能夠帶有反作用,包括執行異步 API 請求。這個函數還能夠 dispatch action,就像 dispatch 前面定義的同步 action 同樣。那麼如何在action中進行網絡請求?標準的作法是使用 Redux Thunk middleware。要引入 redux-thunk 這個專門的庫才能使用。node

搭建工做流

咱們將採用ES6語法,webpack進行打包,webpack-dev-server啓一個本地服務器,而後用HMR技術進行React熱加載,看看webpack配置信息:react

var webpack = require('webpack');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');

module.exports = {
  entry: {
    index: [
      'webpack/hot/dev-server',
      'webpack-dev-server/client?http://localhost:8080',
      './src/index.js',
    ]
  },
  output: {
    path: './build',
    filename: '[name].js',
  },
  devtool: 'source-map',
  module: {
    loaders: [{
      test: /\.js$/,
      loader: 'babel',
      query: {
        presets: ['es2015', 'stage-0', 'react'],
      },
    }, {
      test: /\.less$/,
      loader: 'style!css!less',
    }],
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin(),
    new OpenBrowserPlugin({ url: 'http://localhost:8080' }),
  ]
};

其中open-browser-webpack-plugin插件將會幫助咱們自動打開瀏覽器,用babel進行es編譯,less來維護咱們的css樣式,以及使用dev-tool來生成source map,HotModuleReplacementPlugin來進行熱更新。webpack

再看看咱們最後的目錄結構:git

├── build
│   ├── index.html
│   └── index.js
├── node_modules
├── package.json
├── src
│   ├── actions
│   │   └── actions.js
│   ├── components
│   │   ├── index.js
│   │   ├── Profile
│   │   │   ├── Profile.js
│   │   │   └── Profile.less
│   │   └── Search
│   │       ├── Search.js
│   │       └── Search.less
│   ├── containers
│   │   ├── App.js
│   │   ├── App.less
│   │   └── test.less
│   ├── index.html
│   ├── index.js
│   └── reducers
│       └── reducers.js
└── webpack.config.js

其中containers放置咱們的容器組件,components放置展現性組件,打包入口是index.jsgithub

Demo

Redux

state

使用Redux很是重要的一點就是設計好頂層的state,在demo中咱們須要的state大概長這個樣子:web

{
  isFetchingData, // boolean
  username, // string
  profile, // object
}

其中isFetchingData是網絡請求的狀態,正在拉取數據爲true,username是咱們要獲取用戶信息的名字,profile是咱們拉取用戶的詳細信息,這個將會是一個Ajax請求,最後由github API提供。

actions

同步action咱們再也不講述,上一篇文章已經說得比較清楚,這裏咱們重點說異步action,app的全部action以下:

export const GET_INFO = 'GET_INFO'; // 獲取用戶信息
export const FETCHING_DATA = 'FETCHING_DATA'; // 拉取狀態
export const RECEIVE_USER_DATA = 'RECEIVE_USER_DATA'; //接收到拉取的狀態

// async action creator
export function fetchUserInfo(username) {
  return function (dispatch) {
    dispatch(fetchingData(true));
    return fetch(`https://api.github.com/users/${username}`)
    .then(response => {
      console.log(response);
      return response.json();
    })
    .then(json => {
      console.log(json);
      return json;
    })
    .then((json) => {
      dispatch(receiveUserData(json))
    })
    .then(() => dispatch(fetchingData(false)));
  };
}

上面網絡請求用到了fetch這個API,它會返回一個Promise,還比較新可使用社區提供的polyfill或者使用純粹的XHR都行,這都不是重點。咱們看看這個action生成函數返回了一個函數,而且在這個函數中還有dispatch操做,咱們經過中間件傳入的dispatch能夠用來dispatch actions。在上面的promise鏈式中首先咱們打印了github API返回Response object,而後輸出了json格式的數據,而後dispatch了RECEIVE_USER_DATA這個action表示接收到了網絡請求,並須要修改state(注:這裏咱們沒有考慮網絡請求失敗的狀況),最後咱們dispatch了FETCHING_DATA並告訴對應reducer下一個state的isFetchingData爲false,表示數據拉取完畢。

reducer

這裏看看最核心的reducer,操做profile這一塊的:

function profile(state = {}, action) {
  switch (action.type) {
    case GET_INFO:
      return Object.assign({}, state, {
        username: action.username,
      });
    case RECEIVE_USER_DATA:
      return Object.assign({}, state, action.profile);
    default: return state;
  }
}
function isFetchingData() {...}
function username() {...}
const rootReducer = combineReducers({
  isFetchingData,
  username,
  profile,
});
export default rootReducer;

將拉取到的profile對象assign到以前的state,最後經過combineReducers函數合併爲一個reducer。

鏈接到React

咱們經過react-redux提供的connect方法與Provider來鏈接到React,Provider主要的做用是包裝咱們的容器組件,connect用於將redux與react進行鏈接,connect() 容許你從 Redux store 中指定準確的 state 到你想要獲取的組件中。這讓你能獲取到任何級別顆粒度的數據,瞭解更多能夠參考它的API,這裏咱們再也不敖述。它的形式能夠是這樣:

function mapStateToProps(state) {
  return {
    profile: state.profile,
    isFetchingData: state.isFetchingData,
  };
}

function mapDispatchToProps(dispatch) {
  return {
    fetchUserInfo: (username) => dispatch(fetchUserInfo(username))
  };
}

class App extends Component {
  render() {
    const { fetchUserInfo, profile, isFetchingData } = this.props;
    return (
      <div className='container'>
        <Search fetchUserInfo={fetchUserInfo} isFetchingData={isFetchingData} />
        {'name' in profile ? <Profile profile={profile} isFetchingData={isFetchingData} /> : ''}
      </div>
    );
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

connect是個能夠執行兩次的柯里化函數,第一次傳入的參數至關於一系列的定製化東西,第二次傳入的是你要鏈接的React組件,而後返回一個新的React組件。第一次執行時傳入的參數是mapStateToProps, mapDispatchToProps, mergeProps, options。也就是說這裏至關於幫組容器選擇它在整個Store中所需的state與dispatch回調,這些將會被connect以Props的形式綁定到App容器,咱們能夠經過React開發者工具看到這一點:

clipboard.png

第一次執行,選擇須要的state,第二次傳入App容器組件而後返回新的組件。而後建立整個應用的store:

const loggerMiddleware = createLogger();
const store = createStore(
  rootReducer,
  compose(
    applyMiddleware(
      thunkMiddleware,
      loggerMiddleware,
    ),
    window.devToolsExtension ? window.devToolsExtension() : f => f
  )
);

這裏咱們用到了兩個中間件,loggerMiddleware用於輸出咱們每一次的action,能夠明確的看到每次action後state的先後狀態,thunkMiddleware用於網絡請求處理,最後window.devToolsExtension ? window.devToolsExtension() : f => f是爲了鏈接咱們的redux-dev-tool,能夠明確的看到咱們dispatch的action,還能達到時間旅行的效果。最後經過Provider輸入咱們的store,整個應用就跑起來啦!

let mountRoot = document.getElementById('app');
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  mountRoot
);

Run

命令行輸入npm run dev,整個應用就跑起來了,在輸入框輸入Jiavan,咱們來看看action與數據流:

clipboard.png

在console面板,logger中間件爲咱們打印除了每一次dispatch action以及先後的state值,是否是很是直觀,然而厲害的還在後面。redux-dev-tool能夠直接查看咱們state tree以及對action作undo操做達到時間旅行的效果!

clipboard.png

clipboard.png

完整的demo在文章最後將會貼出,如今總結下:首先咱們規劃了整個應用的state,而後進行數據流層的代碼開發,同步異步action的編寫以及reducer的開發,再經過選擇咱們容器組件所需的state與dispatch回調經過connect方法綁定後輸出新的組件,經過建立store與Provider將其與React鏈接,這樣整個應用的任督二脈就被打通了。最後極力推薦Redux的官方文檔。

完整demo -> https://github.com/Jiavan/rea...

運行

1. npm install
2. webpack
3. npm run dev

參考文章

原文出處 https://github.com/Jiavan/jia... 以爲對你有幫助就給個star吧
相關文章
相關標籤/搜索