react-redux 學習以及模組化配置方案
redux
執行流程圖:

簡單概述: click -> store.dispatch(action) -> reduer -> newState -> viewUpdate
react-readux中 通過 connect 連結元件和 redux , **this.props.dispatch()**呼叫
後面將會講到...
redux
依賴包也是十分的簡潔

demo
const redux = require('redux') const createStore = redux.createStore const types = { UPDATE_NAME: 'UPDATE_NAME' } const defaultStore = { user: 'tom' } /** * reducer 純函式 接收一個state,返回一個新的state * @param {Object} state * @param {Object} action [type] 必選引數 * @return newState * */ function getUser(state = defaultStore, action) { const { type, payload } = action let res = Object.assign({}, defaultStore) switch (type) { case types.UPDATE_NAME: res.user = payload.name break default: return res } return res } const store = createStore(getUser) /** * listener * */ store.subscribe(() => { console.log(store.getState()) }) /** * dispatch(action) action * */ store.dispatch({ type: types.UPDATE_NAME, payload: { name: '大帥哥' } }) //@log { name: '大帥哥' } 複製程式碼
- 使用者發出
action
【store.dispatch(action)
】 -
Store
自動呼叫Reducer
, 返回新的state
【let nextState = getUser(previousState, action)
】 -
State
一旦有變化,Store
就會呼叫監聽函式 【store.subscribe(listener)
】
執行過程如下:

store
Store
就是儲存資料的地方,你可以把它看成一個容器。整個應用只能有一個 Store
常用方法:
- store.dispatch() :分發 action 較為常用
- store.subscribe() : state 發生變化後立即執行
- store.getState() : 獲取store 中存著的state
createStore
createStore 如其名,建立 store
下面是該方法的部分原始碼:
/** * @param {Function} reducer 函式 * @param {any} [preloadedState] The initial state * @param {Function} [enhancer] The store enhancer * @returns {Store} * */ export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)(reducer, preloadedState) } // ... return { dispatch,// 分發 action subscribe, // 監聽器 getState, // 獲取 store 的 state 值 replaceReducer, [$$observable]: observable // 供Redux內部使用 } } 複製程式碼
-
preloadedState
: 初始化的initialState
,第二個引數不是Object
,而是Function
,createStore
會認為你忽略了preloadedState
而傳入了一個enhancer
-
createStore
會返回enhancer(createStore)(reducer, preloadedState)
的呼叫結果,這是常見高階函式的呼叫方式。在這個呼叫中enhancer
接受createStore
作為引數,對createStore
的能力進行增強,並返回增強後的createStore
dispatch(action)
diapatch
是store物件的方法,主要用來分發 action
,
redux規定action一定要包含一個type屬性,且type屬性也要唯一
dispatch 是 store 非常核心的一個方法,也是我們在應用中最常使用的方法,下面是dispatch的原始碼 :
function dispatch(action) { if (!isPlainObject(action)){ // 校驗了action是否為一個原生js物件 throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { // action物件是否包含了必要的type欄位 throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) {// 判斷當前是否處於某個action分發過程中, 主要是為了避免在reducer中分發action throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } const listeners = (currentListeners = nextListeners) for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } // 在一系列檢查完畢後,若均沒有問題,將當前的狀態和action傳給當前reducer,用於生成新的state return action } 複製程式碼
reducer && store.replaceReducer
Redux中負責響應action並修改資料的角色就是 reducer
, reducer
的本質實際上是一個函式 replaceReducer:
/** * @desc 替換當前的reducer的函式 * @param {Function} * @return {void} */ function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) } 複製程式碼
replaceReducer 使用場景:
- 當你的程式要進行程式碼分割的時候
- 當你要動態的載入不同的reducer的時候
- 當你要實現一個實時reloading機制的時候
中介軟體 middleware
以上介紹了redux的實現流的過程,應用場景無非於
button -- click --> disptch
-- action --> reducer
-- newState --> view
但是這種實現方式是基於同步的方式的,日常開發中當然少不了 http 這些非同步請求,這種情況下必須等到伺服器資料返回後才重新渲染 view, 顯然某些時候回阻塞頁面的展示。
舉例來說,要新增日誌功能,把 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 這兩步之間,添加了其他功能。
applyMiddleware
Redux提供了 applyMiddleware
來裝載 middleware
: 它是 Redux 的原生方法,**作用是將所有中介軟體組成一個數組,依次執行。**下面是它的原始碼。
/** * @param {...Function} middlewares * returns {Function} A store enhancer applying the middleware */ export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } } 複製程式碼
所有中介軟體被放進了一個數組chain,然後巢狀執行,最後執行store.dispatch。可以看到,中介軟體內部(middlewareAPI)可以拿到 getState
和 dispatch
這兩個方法
compose
實際上是函數語言程式設計中的組合,接收多個函式體並且將其組合成一個新的函式,例如 compose
後 [fn1, fn2...] 依次從右到左巢狀執行函式 而 compose
用於 applyMiddleware
也是為了組合中介軟體 dispatch = compose(...chain)(store.dispatch) ==> dispatch=fn1(fn2(fn3(store.dispatch)))
/** * @param {...Function} funcs The functions to compose. * @returns {Function} A function obtained by composing the argument functions */ export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args))) } 複製程式碼
redux-thunk
上面的中介軟體的介紹可以知道 redux 通過 applyMiddleware
來裝載中介軟體,通過compose 方法可以組合函式
非同步的問題可以通過 redux-thunk
解決,用法也不難 react 元件中使用相關如下:
// 配置 redux 加上這個... import { createStore, applyMiddleware, compose } from 'redux' import thunk from 'redux-thunk' // ... const store = createStore(getUser, compose( applyMiddleware(thunk) )) // react 中使用 import { connect } from 'react-redux' handleClick = () => { this.props.dispatch( dispatch => { return axios.get('https://randomuser.me/api/').then( res => { dispatch({ type: types.CHANGE_ARRAY, payload: { name: res.data.results[0].name.title } }) } ) } ) } const mapStateToProps = (state, props) => { return { name: state.demo.name } } export default connect( mapStateToProps )(Demo) 複製程式碼
處理非同步的還有很多外掛 如 redux-soga 等,樓主並未實踐過,所以不做延伸...
react-redux
下面是在react中使用的程式碼的雛形:
import { createStore } from 'redux' let defaultState = { count: 1 } /** * Reducer * */ function demoReducer(state = defaultState, action = {}) { const { type, payload } = action const res = Object.assign({}, state) if (type === 'changeCount') { res.count = payload.count } return res } /** * @Store 存資料的地方,你可以把它看成一個容器。整個應用只能有一個 Store。 * combineReducers({ ...reducers }) 可以組合多個reducer * */ const store = createStore( demoReducer, window.devToolsExtension && window.devToolsExtension() // 配置redux 開發工具 ) // ... 根元素下配置下 Provider import { Provider } from 'react-redux' ReactDOM.render( <Provider store={store}> <App/> </Provider>, document.getElementById('root') ) // 元件中使用 import { connect } from 'react-redux' //use this.dispatch({ type: 'changeCount', payload: { count: 22 } }) const mapStateToProps = (state, props) => { return { name: state.demo.name } } export default connect( mapStateToProps )(Demo) 複製程式碼
mapStateToProps
mapStateToProps
mapDispatchToProps
mapDispatchToProps mapDispatchToProps
import { connect } from 'react-redux' import { bindActionCreators } from 'redux' // 頁面中使用... this.props.changeName() const mapDispatchToProps = { changeName } =(dispatch, props) => { return bindActionCreators({ changeName: function() { return { type: types.UPDATE_NAME, payload: { name: '大大大' } } } }, dispatch) } export default connect( mapDispatchToProps )(App) 複製程式碼
模組化配置
下面的配置僅供參考。
安裝
npm install redux react-redux redux-thunk --save npm install immutability-helper --save npm install babel-plugin-transform-decorators-legacy -D 複製程式碼
當state中存放的欄位記憶體未發生改變時,檢視並不會更新。所以借用到了 immutability-helper ,輔助更新 當然也可以替換掉json陣列,這樣便會觸發檢視的更新了。
src 下新建 store 檔案,如下所示:

配置檔案
types.js
檔案
export const UPDATE_NAME = 'UPDATE_NAME' export const UPODATE_ARRAY = 'UPODATE_ARRAY' 複製程式碼
demoModule.js
檔案
import * as types from '../types' import update from 'immutability-helper' const defaultState = { name: '預設值', jsonArray: [{ name: '陣列預設值' }], array: [] } /** * demo reducer * */ export default function demo(state = defaultState, action = {}) { const { type, payload } = action const res = Object.assign({}, state) switch (type) { case types.UPDATE_NAME: res.name = payload.name break case types.UPODATE_ARRAY: res.jsonArray = update(state.jsonArray, { [0]: { $set: { name: payload.name } } }) break default: } return res } 複製程式碼
modeules
資料夾下的 index.js
(模組入口檔案)
import { combineReducers } from 'redux' import demo from './demoModule' const Reducers = combineReducers({ demo }) export default Reducers 複製程式碼
combineReducers
: 用於合併 reducers
store
檔案下的 index
( redux
總入口檔案)
import { createStore, applyMiddleware, compose } from 'redux' import Modules from './modules' import thunk from 'redux-thunk' const store = createStore(Modules, compose( applyMiddleware(thunk), window.devToolsExtension && window.devToolsExtension() // 配置redux 開發工具 )); export default store 複製程式碼
-
Redux
預設只處理同步,非同步任務需要redux-thunk
中介軟體 - 引入
applyMiddleware
來處理中介軟體 - 使用
compose
結合thunk
和window.devToolsExtension
修改src 下 index.js
(渲染根元件的入口檔案)
import { Provider } from 'react-redux' import store from './store' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root')) 複製程式碼
測試: demo.jsx
元件
import React, { Component } from 'react' import { connect } from 'react-redux' import * as types from './types' import axios from 'axios' import { Button } from 'antd' /** * @state store 中的state * @props 元件內接收的props 寫了第二個引數props,那麼當元件內props發生變化的時候,mapStateToProps也會被呼叫 * @return Object * */ const mapStateToProps = (state, props) => { return { name: state.demo.name, jsonArray: state.demo.jsonArray, array: state.demo.array, } } @connect(mapStateToProps) class App extends Component { constructor(props) { super(props) this.state = {} } handleClick = (type) => { switch (type) { case 1: // 常規呼叫 this.props.dispatch({ type: types.UPDATE_NAME, payload: { name: '同步呼叫redux' } }) break case 2: // 非同步請求 this.props.dispatch( dispatch => { return axios.get('https://randomuser.me/api/').then( res => { dispatch({ type: types.UPDATE_NAME, payload: { name: res.data.results[0].name.title } }) } ) } ) break case 3: // 改變json陣列,通過普通方式 this.props.dispatch({ type: types.UPODATE_ARRAY, payload: { name: '這是需要被渲染的 name' } }) break default: } } componentWillReceiveProps() { console.log('props change') } render() { const { name, jsonArray } = this.props return ( <div> name: {name} <br/> jsonArray[0]['name'] : {jsonArray[0]['name']}<br/> <Button onClick={e => this.handleClick(1)}>常規呼叫</Button> <Button onClick={e => this.handleClick(2)}>非同步呼叫</Button> <Button onClick={e => this.handleClick(3)}>改變json陣列</Button> </div> ) } } export default App 複製程式碼
上面用了裝飾器模式來 connect , 配置一下 babel 就好了
"plugins": [ "transform-decorators-legacy" ] 複製程式碼
以上配置,僅供參考,網上有更多更好的,也求大家推薦一下更好地配置。這是一篇學習記錄,學海無涯,希望自己加油...
參考
- 阮一峰 redux 入門教程
- 配置方案相關檔案 store 資料夾