react redux在項目中的使用

 

一開始我接觸到redux是很懵的,看官方文檔也很枯燥乏味,不理解說的什麼意思,也是看過就忘,所以就結合公司的項目來熟悉一下redux。也可能有寫的不對的地方,歡迎指出。

一、構建reducer

首先看storeConfig下的reducers.js。

combineReducers函數的作用是把一個或多個的reducer結合成一個reducer,作爲createStore函數的第一個參數。

使用方法是:

combineReducers({ key1 : reducer1, key2 : reducer2 })

最終這個函數的返回的state的結構就是 {key1,key2},這個key值是可以自定義的,如果懶得再次命名,就可以使用ES6的簡潔語法 combineReducer(reducer1, reducer2);返回的state就是 {reducer1, reducer2}的形式。

由於項目很龐大,在一開始就初始化所有的store是很繁重的,這裏用到了動態加載redux模塊的思想,把各個模塊的redux分塊存儲,在項目首次加載時只加載一部分reducer(如layout 登錄狀態、全局配置,home首頁的數據)。

上圖的第三個參數asyncReducer代表的就是接收異步加載的模塊的reducer,注入到根reducer中,這種異步按需加載reducer的方式也稱作redux的動態注入

如何加載異步模塊,就是通過路由了,具體是如何實現的呢,我們來看路由的總文件routes\index.js,這個文件包含了所有模塊的頁面。

以申請模塊爲例,我們看看申請模塊下的路由總文件\routes\ApproveV2\index.js,它裏面包含了申請模塊下所有路由的頁面,是對整個模塊的路由的進一步分發,我們需要再進一步看。

來看申請列表頁\routes\ApproveV2\ApplyEntry.js,這個頁就是如何把引入相應的reducer並傳給根reducer。

  • 首先引入redux配置中的injectReducer
  • 定義頁面路由
  • 通過getComponent+webpack ensure實現在路由跳轉時,異步加載該模塊
  • 引入該模塊相應的reducer
  • 通過injectReducer函數,傳入兩個參數。
    • 第一個參數爲store,因爲異步獲取的redux模塊的reducer都保存在store.asyncReducer中,最後在makeRootReducer函數中通過擴展運算符給了combineReducers。
    • 第二個參數爲一個對象,鍵有key和reducer,還記得我們之前說的向combineReducers函數傳遞reducer函數時可以自定義reducer的名字嗎,key就是指定的申請列表的名字爲applyEntry,這樣state中就有了state.applyEntry,並傳入引入的模塊的reducer
  • 加載該組件,進行路由的跳轉。

那麼在初始化組件之前,數據就需要從之前的  變爲這樣

把異步獲取的reducer和原來的reducer結合在一起,是如何實現的呢?繼續看這個文件storeConfig\reducers.js

它把接收來的store.asyncReducers傳入一開始的makeRootReducer函數,通過replaceReducer(nextReducer)函數來立即加載替換原來的reducer,從而改變state爲新的數據結構,調用combineReducer函數,生成新的根reducer。

打印store,我們可以看到在申請的injectReducer之前只有home

 

在申請的injectReducer之後就加上了申請的reducer

 

二、創建Redux store

接下來就來看 storeConfig\createStore.js文件,

創建store的函數就是createStore,

用法如下:

createStore(reducer,[preloadedState],enhancer)

第一個參數是是reducer,傳入我們上面介紹過的reducers.js文件提供的makeRootState。

第二個參數是初始的state,傳入的是一個空對象,

第三個參數是一個組合 store creator 的高階函數,返回一個新的強化過的 store creator,可能不知道增強器是什麼,那我們先來看他傳入的是個什麼東西,

compose(...functions) 把傳入的函數參數從右向左組合成一個最終函數,右邊的函數執行完就作爲一個參數傳遞給左邊的函數。

我們來看一下這個函數怎麼實現的,

可以看到函數內部使用的是reduce函數,它是這樣說的:

reduce() 方法接收一個函數作爲累加器,數組中的每個值(從左到右)開始縮減,最終計算爲一個值。

reduce() 可以作爲一個高階函數,用於函數的 compose。

這也就驗證了enhancer是一個高階函數

舉個例子:

給compose傳入四個參數

compose(func1,func2,func3,func4)

就會執行核心代碼

funcs.reduce((a, b) => (...args) => a(b(...args)))

也就是

[func1,func2,func3,func4].reduce((a, b) => (...args) => a(b(...args)))

reduce函數從左向右執行,a參數代表上一次執行的返回值,b參數代表數組當前遍歷到的值,b參數就作爲下一次執行的參數傳入。

這樣最終的結果就是,最右邊的函數可以傳入任意參數,在外部調用的時候傳入,其他函數都只能接收一個參數,就是上一次函數返回的結果。

func1(func12(func3(func4(...args))))

也就驗證了compose的定義:把傳入的函數參數從右向左組合成一個最終函數

下面我們來具體傳入的是什麼函數:

相關代碼如下:

 

可以看到用到了applyMiddleware,redux-thunk,redux-logger。

先來看傳入applyMiddleware函數的middleware參數是redux-thunk,它是一個現成的“中間件”庫,我們先不管這個庫具體怎麼實現的,先了解一下中間件的概念,它可以提供位於 action 被髮起之後,到達 reducer 之前的擴展點,就是如果我們不想讓dispatch action之後,就立馬更新state,而是在執行過程中可以做一些操作,如打印日誌,異步發送ajax請求等。它的作用就是改造dispatch,在dispatch發出一個action之前,和action到達reducer之間添加一些東西。ps:具體怎麼實現的,我實在是理解有限,還沒搞清楚,想了解的再仔細看看官方文檔。

如下面這個最簡單的例子:

function dispatchAndLog(store, action) {
   console.log('dispatching', action)
   store.dispatch(action)
   console.log('next state', store.getState())
}

引入redux-thunk庫的作用就是用來解決異步問題的,單純的redux,在dispatch action發出之後,就立即計算更新state,這是同步,而在我們的項目中,往往有用戶的操作需要請求接口來更新state,這時候就需要異步來執行action,原本的store.dispatch只能接收action對象,但是在經過redux-thunk的處理之後,store.dispatch可以接收一個函數作爲參數,並且這個函數帶有dispatch和getState參數

如項目中的例子,可以看到能接收一個函數fetchIndexInfo,而這個函數就是action creater,用來請求接口、獲取數據的

在store.dispatch執行完傳入的函數中的axios請求之後,還會返回一個dispatch,用來繼續執行redux事件,

// 即把獲取的數據存入store中

dispatch(updateIndexInfo(result.data));

export const updateIndexInfo = (value) => ({
   type: HOME_UPDATE_INDEX_INFO,
   payload: value
});

以上就實現了異步更新state,這是我們系統最核心的地方。

而redux-logger就是一個可以打印日誌的中間件,在我們執行dispatch時,他會打印出dispatch具體的執行過程,並且注意要放在所有中間件的最後,防止先執行了,無法打印出日誌的情況。

可以看到在控制檯打印如下:

在構造好所有的中間件之後,需要通過applyMiddlerware函數來把他們組合成一個數組,從右向左順序執行。實現的原理也是我們上面介紹過的compose函數,會把原來的dispatch函數替換爲會遍歷執行所有的middleware的新的dispatch函數,這樣每次執行dispatch時都會經過一次次的middlewares的調用,最終會生成新的dispatch,並return出去。

來看源碼,我看了好幾遍也看不懂,感興趣的可以深入研究一下。

以上介紹了createStore中的第三個參數enhancer的中的第一個參數applyMiddleware,我們來看第二個參數enhancers,devToolsExtension是一個觀察redux中狀態變化的一個谷歌插件。

if (__DEV__) {
    const devToolsExtension = window.devToolsExtension
    if (typeof devToolsExtension === 'function') {
       enhancers.push(devToolsExtension())
   }
}

通過compose函數把多個增強器:中間件和調試工具組合,嵌套執行。

以上就介紹完了createStore中傳入的第三個參數,那這個參數是幹什麼用的呢,上面介紹了對middlerware的強化,它是對createStore方法進行強化的,返回功能加強後的新的store。

三、使用store

  • 在main.js文件中,引入上面創建好的createStore函數,進行實例化,創建好的store會暴露出一些API。
  • getState()用來獲取應用的state樹,
  • subscribe()是是一個變化監聽器,在dispatch action之後,state會發生變化,在它的回調函數中,通過getState()重新獲取state,賦值給全局變量。
  • 同時還要引入創建好的路由,
  • 把store和routes傳入項目的根組件AppContainer,掛載到創建好的dom節點上。

下面來具體看一下AppContainer組件

引入的庫如下:

import PropTypes from 'prop-types';

import React, { Component } from 'react';
import {browserHistory, Router} from 'react-router'
import { Provider } from 'react-redux'

1.  PropTypes是react內置的類型檢查工具,用來對組件上的props進行類型檢查。

使用方法如下:

項目中要求傳入AppContainer的對象是必填的。

AppContainer.propTypes = {
    routes: PropTypes.object.isRequired
};

2. react router是基於history的,history監聽瀏覽器地址欄的變化,然後router根據它來匹配路由,從而渲染正確的組件。

它有三種形式,browserHistory、hashHistory、createMemoryHistory,項目中使用的是browserHistory,它創建的URL是類似於example.com/some/path。

3.接下來看最關鍵的Provider,通過它包裹整個項目的根組件,並且傳入Store對象,在相應的容器組件內才能通過connect來結合React和Redux。

關鍵之處就在於connect()函數,用法如下:

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

  • 第一個參數:mapStateToProps(state, [ownProps]): stateProps (Function)

根據名字來看,是對state到props做一個映射,這個函數接收兩個參數,第一個參數是應用的state,第二個參數顧名思義指的是本組件的props,函數必須返回一個純對象,這個對象就和組件的props組合,形成新的props,每當state變化,或者組件中的props變化的時候,mapStateToProps都會被調用。

例如組件ReduxForm中:

對本組件的props進行處理,獲取到state中相應的數據,然後賦值給formReducerVal,那麼組件的props就會多出這個屬性。

  • 第二個參數:mapDispatchToProps(dispatch, [ownProps]): dispatchProps  (Object or Function)

根據名字意思就是把dispatch action也映射到組件的props上,如同把state映射到props上一樣。

通常我們調用action時,需要由store上的dispatch來觸發,下面的例子就把aciton和dispatch合成了一個值,作爲當前組件的屬性,使得可以在組件中通過this.props的方式訪問action

const mapDispatchToProps = (dispatch, ownProps) => {
    return {
        increase: (...args) => dispatch(actions.increase(...args)),
        decrease: (...args) => dispatch(actions.decrease(...args))
    }
}
//在組件中訪問action的方式

this.props.increase();

this.props.decease();

redux官方給提供了一個現成的方法,可以實現同樣的效果。

const mapDispatchToProps = (dispatch, ownProps) => {
     return {
           increase: (...args) => dispatch(actions.increase(...args)),
           decrease: (...args) => dispatch(actions.decrease(...args))
     }
}

在我們的項目中是這樣使用的,所以在組件中直接通過 this.props.dispatch的方式調用action,並沒有進行過多的處理。

const mapDispatchToProps = (dispatch) => {
   return {dispatch}
};

 

const mapDispatchToProps = (dispatch) => {

    return {

         ...bindActionCreators(actions,dispatch), dispatch

    }

}

  • 第三個參數 mergeProps(stateProps, dispatchProps, ownProps): props  (Function)

它是接收前面兩個參數的執行結果的回調函數,並會把返回值和當前的props合併

  • 第四個參數options (Object)   用來定製 connector 的行爲

 

以上就完成了react使用redux的基本步驟。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章