深入淺出Redux實現原理


1.Redux應用場景

在react中,數據在組件中單向流動的,數據只能從父組件向子組件流通(通過props),而兩個非父子關系的組件之間通信就比較麻煩,redux的出現就是為了解決這個問題,它將組件之間需要共享的數據存儲在一個store里面,其他需要這些數據的組件通過訂閱的方式來刷新自己的視圖。

2.Redux設計思想

它將整個應用狀態存儲到store里面,組件可以派發(dispatch)修改數據(state)的行為(action)給store,store內部修改之后,其他組件可以通過訂閱(subscribe)中的狀態state來刷新(render)自己的視圖。

 

 

 畫圖舉栗子:

組件B,C需要根據組件A的數據來修改自己的視圖,組件A不能直接通知組件BC,這個時候就可以將數據存儲到sore里面,然后A組件向store里面的處理函數派發指令,reducer接收到指令action后修改state數據,組件BC可以通過訂閱來修改自己的視圖。

3.Redux應用的三大原則

    • 單一數據源
      我們可以把Redux的狀態管理理解成一個全局對象,那么這個全局對象是唯一的,所有的狀態都在全局對象store下進行統一”配置”,這樣做也是為了做統一管理,便於調試與維護。
    • State是只讀的
      與React的setState相似,直接改變組件的state是不會觸發render進行渲染組件的。同樣,在Redux中唯一改變state的方法就是觸發action,action是一個用於描述發生了什么的“關鍵詞”,而具體使action在state上更新生效的是reducer,用來描述事件發生的詳細過程,reducer充當了發起一個action連接到state的橋梁。這樣做的好處是當開發者試圖去修改狀態時,Redux會記錄這個動作是什么類型的、具體完成了什么功能等(更新、傳播過程),在調試階段可以為開發者提供完整的數據流路徑。
    • Reducer必須是一個純函數
      Reducer用來描述action如何改變state,接收舊的state和action,返回新的state。Reducer內部的執行操作必須是無副作用的,不能對state進行直接修改,當狀態發生變化時,需要返回一個全新的對象代表新的state。這樣做的好處是,狀態的更新是可預測的,另外,這與Redux的比較分發機制相關,閱讀Redux判斷狀態更新的源碼部分(combineReducers),發現Redux是對新舊state直接用==來進行比較,也就是淺比較,如果我們直接在state對象上進行修改,那么state所分配的內存地址其實是沒有變化的,“==”是比較對象間的內存地址,因此Redux將不會響應我們的更新。之所以這樣處理是避免對象深層次比較所帶來的性能損耗(需要遞歸遍歷比較)。

 4.源碼實現:

4.1  creatStore

 
export default function createStore(reducrer,initialState){ let state = initialState //狀態
    let listeners = [] //獲取當前狀態
    function getState() { return state } //派發修改指令給reducer 
    function dispatch(action) { //reducer修改之后返回新的state
      state = reducrer(state,action) //執行所有的監聽函數
      listeners.forEach(listener => listener()) } //訂閱 狀態state變化之后需要執行的監聽函數
    function subscribe(listener) { listeners.push(listener) //監聽事件
      return function () { let index = listeners.indexOf(listener) listeners.splice(index,1) } } //在倉庫創建完成之后會先派發一次動作,目的是給初始化狀態賦值
    dispatch({type:'@@REDUX_INIT'}) return { getState, dispatch, subscribe } }

 

解釋一下上面代碼:

倉庫store里面存儲着狀態state和處理器reducer,這兩個參數都是需要外界提供的,當外界想要修改這個state的時候需要通過調用dispatch方法派發action給reducer,reducer會根據action修改state,返回修改之后的值。並且執行組件訂閱的監聽事件函數。

看一個具體的栗子:

html代碼:

  <body>
    <div id="root"></div>
    <button id="increment">+</button>
    <button id="decrement">-</button>
  </body>

js代碼:

import {createStore} from './redux' const INCREMENT = 'INCREMENT' const DECREMENT = 'DECREMENT' let initailState = {number:0} //處理函數
function reducer(state = initailState,action) { switch(action.type) { case INCREMENT: return {number: state.number + 1} case DECREMENT: return {number: state.number - 1} default: return state; } } //創建倉庫
let store = createStore(reducer) let root = document.getElementById('root') let increment = document.getElementById('increment') let decrement = document.getElementById('decrement') //訂閱
store.subscribe( () => { root.innerHTML = store.getState().number }) //點擊的時候派發修改指令action
increment.addEventListener('click', () => { store.dispatch({type: INCREMENT}) }) decrement.addEventListener('click', () => { store.dispatch({type:DECREMENT}) })

點擊 + 加一,點擊 - 減一。

1.先創建好處理函數reducer(),告訴如何修改state,當派發的指令dispatch(),當action type = INCREMENT 的時候加一,當派發的指令 action type = DECREMENT的時候減一;

2.外界訂閱store.subscribe():當狀態發生變化之后修改視圖界面 root.innerHTML = store.getState().number 

函數組件和類組件使用對比:

action_type.js

 

 export const ADD = 'ADD' export const MINUS = 'MINUS'

 

reducer.js

import * as TYPES from './actions_type' let initialState = {number: 0} export default function reducer (state = initialState, action) { switch (action.type) { case TYPES.ADD: return {number: state.number + 1} case TYPES.MINUS: return {number: state.number - 1} default: return state } }

store.js

import {createStore} from 'redux' import reducer from './reducer' const store = createStore(reducer) export default store

組件Couter.js

import React, {useState,useEffect} from 'react' import store from '../store' import * as TYPES from '../store/actions_type'
//類組件寫法:
export default class Couter extends React.Component { state = {number: store.getState().number} componentDidMount() { //當狀態發生變化后會讓訂閱函數執行,會更新當前組件狀態,狀態更新之后就會刷新組件
        this.unSubscribe = store.subscribe( () => { this.setState({number: store.getState().number}) }) } //組件銷毀的時候取消監聽函數
 componentWillUnmount() { this.unSubscribe() } render() { return ( <div>
                <p>{this.state.number}</p>
                <button onClick={()=> store.dispatch({type:TYPES.ADD})}>+</button>
                <button onClick={()=> store.dispatch({type:TYPES.MINUS})}>-</button>
            </div>  
 ) } } //函數組件寫法:
export default function Couter (props) { let [number,setNumber] = useState(store.getState().number) //訂閱 
    useEffect( () => { return store.subscribe( () => { //這個函數會返回一個銷毀函數,此銷毀函數會自動在組件銷毀的時候調用 
 setNumber(store.getState().number) }) },[]) //useEffect的第二個參數是依賴變量的數組,當這個依賴數組發生變化的時候才會執行函數
    return ( <div>
            <p>{store.getState().number}</p>
            <button onClick={()=> store.dispatch({type:TYPES.ADD})}>+</button>
            <button onClick={()=> store.dispatch({type:TYPES.MINUS})}>-</button>
        </div>  
 ) } /** * 對於組件來說倉庫有兩個作用 * 1.輸出:把倉庫中的狀態在組件中顯示 * 2.輸入:在組件里可以派發動作給倉庫,從而修改倉庫中的狀態 * 3.組件需要訂閱狀態變化事件,當倉庫中的狀態發生改變之后需要刷新組件 */

對於Couter組件分別用函數組件的方式和類組件的方式實現,他們最大區別在於訂閱功能的實現,類組件有自己的狀態state,可以通過setState方法修改狀態。但是函數組件並沒有狀態,要想在函數組件中使用狀態就需要通過hooks來實現。

useState這個hooks可以實現狀態。number代表當前的狀態值,setNumber代表改變狀態的方法。

useEffect這個hooks可以用於處理組件中的effect,通常用於請求數據,事件處理,訂閱等相關操作。

 

4.2 bindActionCreators

用法:

對以上栗子改寫使用bindActionCreators:

actions_type.js

function add() { return {type:TYPES.ADD} } function minus() { return {type:TYPES.MINUS} } export default { add, minus }

Counter.js

import React, {useState,useEffect} from 'react' import store from '../store' import actions from '../store/actions_type' import { bindActionCreators } from 'redux' let boundActions = bindActionCreators(actions, store.dispatch) //類組件
export default class Couter extends React.Component { state = {number: store.getState().number} componentDidMount() { //當狀態發生變化后會讓訂閱函數執行,會更新當前組件狀態,狀態更新之后就會刷新組件
        this.unSubscribe = store.subscribe( () => { this.setState({number: store.getState().number}) }) } //組件銷毀的時候取消監聽函數
 componentWillUnmount() { this.unSubscribe() } render() { return ( <div>
                <p>{this.state.number}</p>
                <button onClick={boundActions.add}>+</button> <button onClick={boundActions.minus}>-</button>
            </div>  
 ) } }

源碼實現:(簡單版本)

 

export default function (actionCreators,dispatch) { let boundActionsCreators = {} //循環遍歷重寫action
    for(let key in actionCreators) { boundActionsCreators[key] = function(...args) { //其實dispatch方法會返回派發的action
            return dispatch(actionCreators[key](...args)) } } return boundActionsCreators }

 

可以看到

bindActionCreator 的作用其實就是用來將一個對象的值是action creators轉成一個同樣key的對象,但是轉化的這個對象的值,是將action creator包裹在dispatch里的函數。
完整版本的源碼:
/** 參數說明: actionCreators: action create函數,可以是一個單函數,也可以是一個對象,這個對象的所有元素都是action create函數 dispatch: store.dispatch方法 */ export default function bindActionCreators(actionCreators, dispatch) { // 如果actionCreators是一個函數的話,就調用bindActionCreator方法對action create函數和dispatch進行綁定
  if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } // actionCreators必須是函數或者對象中的一種,且不能是null
  if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } // 獲取所有action create函數的名字
  const keys = Object.keys(actionCreators) // 保存dispatch和action create函數進行綁定之后的集合
  const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] // 排除值不是函數的action create
    if (typeof actionCreator === 'function') { // 進行綁定
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } // 返回綁定之后的對象
  /** boundActionCreators的基本形式就是 { actionCreator: function() {dispatch(actionCreator.apply(this, arguments))} } */
  return boundActionCreators }

 4.3combineReducer

 * redux規定一個應用只能有一個store,倉庫里只能有一個狀態state
 * 每個組件都有自己得狀態和reducer和動作,最后需要合並成一個reducer

combineReducer就是用來合並reducer的。

看個栗子:

兩個組件:counter1和counter2

actions_type.js

 export const ADD = 'ADD' export const MINUS = 'MINUS' export const ADD1 = 'ADD1' export const MINUS1 = 'MINUS1' export const ADD2 = 'ADD2' export const MINUS2 = 'MINUS2'

actions動作Counter1.js

 

import * as TYPES from '../actions_type' export default { add() { return {type: TYPES.ADD1} }, minus() { return {type: TYPES.MINUS1} } }

actions動作Counter2.js

import * as TYPES from '../actions_type' export default { add() { return {type: TYPES.ADD2} }, minus() { return {type: TYPES.MINUS2} } }

reducer Counter1.js

import * as TYPES from '../actions_type' let initialState = {number: 0} export default function reducer (state = initialState, action) { switch (action.type) { case TYPES.ADD1: return {number: state.number + 1} case TYPES.MINUS1: return {number: state.number - 1} default: return state } }

reducer Counter2.js

import * as TYPES from '../actions_type' let initialState = {number: 0} export default function reducer (state = initialState, action) { switch (action.type) { case TYPES.ADD2: return {number: state.number + 1} case TYPES.MINUS2: return {number: state.number - 1} default: return state } }

合並后的reducer index.js

import {combineReducers} from '../../redux' import counter1 from './Counter1' import counter2 from './Counter2'

 let reducers = { counter1:counter1, counter2:counter2 } //合並
let combinedReducer = combineReducers(reducers) export default combinedReducer 

組件Counter1.js

import React, {useState,useEffect} from 'react' import store from '../store'
// import actions from '../store/actions_type'
import { bindActionCreators } from 'redux' import actions from '../store/actions/Counter1' let boundActions = bindActionCreators(actions, store.dispatch)  //函數組件
export default function Couter (props) { let [number,setNumber] = useState(store.getState().counter1.number) //訂閱 
    useEffect( () => { return store.subscribe( () => { //這個函數會返回一個銷毀函數,此銷毀函數會自動在組件銷毀的時候調用 
 setNumber(store.getState().counter1.number) }) },[]) //useEffect的第二個參數是依賴變量的數組,當這個依賴數組發生變化的時候才會執行函數
    return ( <div>
            <p>{number}</p>
            <button onClick={boundActions.add}>+</button>
            <button onClick={boundActions.minus}>-</button>
        </div>  
 ) }

組件 Counter2.js

import React, {useState,useEffect} from 'react' import store from '../store'
// import actions from '../store/actions_type'
import { bindActionCreators } from 'redux' import actions from '../store/actions/Counter2' let boundActions = bindActionCreators(actions, store.dispatch)  //函數組件
export default function Couter (props) { let [number,setNumber] = useState(store.getState().counter2.number) //訂閱 
    useEffect( () => { return store.subscribe( () => { //這個函數會返回一個銷毀函數,此銷毀函數會自動在組件銷毀的時候調用 
 setNumber(store.getState().counter2.number) }) },[]) //useEffect的第二個參數是依賴變量的數組,當這個依賴數組發生變化的時候才會執行函數
    return ( <div>
            <p>{number}</p>
            <button onClick={boundActions.add}>+</button>
            <button onClick={boundActions.minus}>-</button>
        </div>  
 ) }
combineReducers的實現:
/** * 合並rreducer * 1.拿到子reducer,然后合並成一個reducer * @param {*} state * @param {*} action */ export default  function combineReducers(reducers) { //state是合並后得state = {counter1:{number:0},counter2:{number:0}}
    return function (state={}, action) { let nextState = {} // debugger
        for(let key in reducers) { let reducerForKey = reducers[key] //key = counter1,
            //老狀態
            let previousStateForKey = state[key] //{number:0}
            let nextStateForKey = reducerForKey(previousStateForKey,action) //執行reducer,返回新得狀態
            nextState[key] = nextStateForKey //{number: 1}
 } return nextState } }

combineReducers 函數的作用是,把一個由多個不同 reducer 函數作為 value 的 object,合並成一個最終的 reducer 函數,然后就可以對這個 reducer 調用 createStore。

合並后的 reducer 可以調用各個子 reducer,並把它們的結果合並成一個 state 對象。state 對象的結構由傳入的多個 reducer 的 key 決定。

 4.4 redux connect 

上面得栗子中每個組件要向使用store必須是每個都自己引入,有點麻煩,react提供了一個對象Provider和connect,可以通過這兩個對象把react組件和store連接起來,不必再每個都引入。如果想在某個子組件中使用Redux維護的store數據,它必須是包裹在Provider中並且被connect過的組件,Provider的作用類似於提供一個大容器,將組件和Redux進行關聯,在這個基礎上,connect再進行store的傳遞。

如下:

import React from 'react' import ReactDOM from 'react-dom' import Couter1 from './components/Counter1' import Couter2 from './components/Counter2' import {Provider} from './react-redux' import store from './store' ReactDOM.render( <Provider store={store}>
        <Couter1 /><Couter2 />
    </Provider>,document.getElementById('root'))

我們想要在Counter1和Counter2中使用store中得數據,就需要把他們包裹在Provider中, store會作為Provider的屬性props,以上下文的形式傳遞給下層組件。下層組件要想獲取store,不需要再自己引入了,直接從上下文中取就可以了

創建上下文:
import React from 'react' import {creatContext} from 'react' let ReactReduxContext = React.createContext() //創建上下文
export default ReactReduxContext
Counter1.js
export default function Couter (props) { let {store} = useContext(ReactReduxContext) //從上下文中拿到store
    let [number,setNumber] = useState(store.getState().counter1.number) //訂閱 
    useEffect( () => { return store.subscribe( () => { //這個函數會返回一個銷毀函數,此銷毀函數會自動在組件銷毀的時候調用 
 setNumber(store.getState().counter1.number) }) },[]) //useEffect的第二個參數是依賴變量的數組,當這個依賴數組發生變化的時候才會執行函數
    return ( <div>
            <p>{number}</p>
            <button >+</button>
            <button >-</button>
        </div>  
 ) }

Counter2.js

export default function Couter (props) { let {store} = useContext(ReactReduxContext) let [number,setNumber] = useState(store.getState().counter2.number) //訂閱 
    useEffect( () => { return store.subscribe( () => { //這個函數會返回一個銷毀函數,此銷毀函數會自動在組件銷毀的時候調用 
 setNumber(store.getState().counter2.number) }) },[]) //useEffect的第二個參數是依賴變量的數組,當這個依賴數組發生變化的時候才會執行函數
    return ( <div>
            <p>{number}</p>
            <button >+</button>
            <button >-</button>
        </div>  
 ) }

 

通過 useContext 拿到上下文傳過來得值。

用connect改寫上面的Counter1和Counter2:

import React, {useState,useEffect,useContext} from 'react'
// import store from '../store' // import actions from '../store/actions_type'
import { bindActionCreators } from 'redux' import actions from '../store/actions/Counter1' import ReactReduxContext from '../react-redux/context' import {connect} from '../react-redux'
 //函數組件
function Couter (props) {
    return ( <div>
            <p>{props.number}</p>
            <button onClick={props.add}>+</button>
            <button onClick={props.minus}>-</button>
        </div>  
 ) }  let mapStateToProps = state => state.counter1 //從store中拿到當前組件得屬性
 let mapDispatchToProps = actions //把當前組件得動作進行派發
 export default connect( mapStateToProps, actions )(Couter) 

Counter2也是如此。

* mapStateToProps 把當前組件的狀態映射為當前組件的屬性對象 {counter1:{number: 0},counter2: {number:0}} * mapDispatchToProps connect 內部會把actions進行綁定,然后把綁定結果對象作為當前組件的屬性對象,直接在綁定事件的時候用props.add或者props.minus

分別看一下Provider和connect的源碼實現:

Provider.js

import React from 'react' import ReactReduxContext from './context'
/** * Provider 有個store屬性,需要向下傳遞這個屬性 * @param {*} props */ export default function (props) { return ( <ReactReduxContext.Provider value={{store:props.store}}> {props.children} </ReactReduxContext.Provider>
 ) }

connect.js

import React, {useContext, useState, useEffect} from 'react' import ReactReduxContext from './context' import { bindActionCreators } from 'redux'
 export default function (mapStateToProps,mapDispatchToProps) { return function(OldComponent){ //返回一個組件
        return function(props) { //獲取state
            let context = useContext(ReactReduxContext) //context.store
            let [state,setState] = useState(mapStateToProps(context.store.getState())) //利用useState只會在初始化的時候綁定一次
            let [boundActions] = useState( () => bindActionCreators(mapDispatchToProps,context.store.dispatch)) //訂閱事件
            useEffect(() => { return context.store.subscribe(() => { setState(mapStateToProps(context.store.getState())) }) },[]) //派發事件 這種方式派發事件的時候每次render都會進行一次事件的綁定,耗費性能
            // let boundActions = bindActionCreators(mapDispatchToProps,context.store.dispatch)
            //返回組件
            return <OldComponent {...state} {...boundActions} />
 } } }
 * connect 是個高階組件
 * 1.獲取從上下文傳過來得值 store
 * 2.將store.getState()=>mapStateToProps 成為OldComponent得屬性對象
 * 3.負責訂閱store狀態變化事件,當倉庫狀態發生變化后要刷新當前組件以及OldComponent
 * 4.把actions進行綁定,然后把綁定后得結果boundActions作為屬性對象傳遞給OldComponent

connenct並不會改變它“連接”的組件,而是提供一個經過包裹的connect組件。 conenct接受4個參數,分別是mapStateToProps,DispatchToProps,mergeProps,options(使用時注意參數位置順序)。

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

1.mapStateToProps(state, ownProps) 方法允許我們將store中的數據作為props綁定到組件中,只要store更新了就會調用mapStateToProps方法,mapStateToProps返回的結果必須是object對象,該對象中的值將會更新到組件中

const mapStateToProps = (state) => { return ({ count: state.counter.count }) }

2.mapDispatchToProps(dispatch, [ownProps]) 第二個參數允許我們將action作為props綁定到組件中,mapDispatchToProps希望你返回包含對應action的object對象

const mapDispatchToProps = (dispatch, ownProps) => { return { increase: (...args) => dispatch(actions.increase(...args)), decrease: (...args) => dispatch(actions.decrease(...args)) } } export default connect(mapStateToProps, mapDispatchToProps)(yourComponent)

3.mergeProps(stateProps, dispatchProps, ownProps) 該參數非必須,redux默認會幫你把更新維護一個新的props對象,類似調用Object.assign({}, ownProps, stateProps, dispatchProps)。

4.[options] (Object)
如果指定這個參數,可以定制 connector 的行為。

 4.5 redux 中間件middlewares

正常我們的redux是這樣的工作流程,action -> reducer ,這相當於是同步操作,由dispathch觸發action之后直接去reducer執行相應的操作。但有時候我們會實現一些異步任務,像點擊按鈕 -> 獲取服務器數據 ->渲染視圖,這個時候就需要引入中間件改變redux同步執行流程,形成異步流程來實現我們的任務。有了中間件redux的工作流程就是action -> 中間件 -> reducer ,點擊按鈕就相當於dispatch 觸發action,接着就是服務器獲取數據middlewares執行,成功獲取數據后觸發reducer對應的操作,更新需要渲染的視圖數據。

中間件的機制就是改變數據流,實現異步acation,日志輸出,異常報告等功能。

4.5.1 日志中間件

希望在store狀態變更之前打印日志

//1.備份原生的dispatch方法
let dispatch = store.dispatch //2.重寫dispatch方法 做一些額外操作
store.dispatch = function (action) { console.log('老狀態',store.getState()) //觸發原生dispatch方法
 dispatch(action) console.log('新狀態', store.getState()) }

在重寫dispatch方法之前先備份原生的dispatch方法,這個寫法和vue中監聽數組的變化方式很相似。

這種寫法是直接對dispatch進行重寫,不利於維護,當有多個中間件的時候也沒法調用,所以要改成下面的寫法

import {createStore} from 'redux' import reducer from './reducers/Counter' const store = createStore(reducer) //1.備份原生的dispatch方法 // let dispatch = store.dispatch // //2.重寫dispatch方法 做一些額外操作 // store.dispatch = function (action) { // console.log('老狀態',store.getState()) // //觸發原生dispatch方法 // dispatch(action) // console.log('新狀態', store.getState()) // }

function logger ( {dispatch, getState}) { //dispatch是重寫后的dispatch
    return function (next) { //next代表原生的dispatch方法,調用下一個中間件或者store.dispatch 級聯
        //改寫后的dispatch方法
        return function (action) { console.log('老狀態', getState()) next(action) //store.dispatch(action)
            console.log('新狀態', getState()) dispatch(action) //此時的disptch是重寫后的dispach方法,這樣會造成死循環
 } } } function applyMiddleware(middleware) { //middleware = logger
    return function(createStore) { return function (reducer) { let store = createStore(reducer) // 返回的是原始的未修改后的store
 let dispatch middleware = middleware({ //logger執行 需要傳參getState 和 dispatch 此時的 middleware = function(next)
 getState: store.getState, dispatch: action => dispatch(action) //指向改寫后的新的dispatch 不能是store.dispatch
 }) dispatch = middleware(store.dispatch) //執行上面返回的middleware ,store.dispatch 代表next
            return { ...store, dispatch } } } } let store = applyMiddleware(logger)(createStore)(reducer) export default store 

4.5.2 thunk中間件

正常的actions必須是一個純對象,如{type:'add'},不能是函數function,但有時候我們希望是自己寫的函數,這個時候就可以用thunk這個中間件了,
function thunk ({dispatch, getState}) { return function (next) { return function (action) { if(typeof action === 'function') { action(dispatch, getState) }else { next(action) } } } } function applyMiddleware(middleware) { //middleware = logger
    return function(createStore) { return function (reducer) { let store = createStore(reducer) // 返回的是原始的未修改鍋的store
 let dispatch middleware = middleware({ //logger執行 需要傳參getState 和 dispatch 此時的 middleware = function(next)
 getState: store.getState, dispatch: action => dispatch(action) //指向改寫后的新的dispatch 不能是store.dispatch
 }) dispatch = middleware(store.dispatch) //執行上面返回的middleware ,store.dispatch 代表next
            return { ...store, dispatch } } } } let store = applyMiddleware(thunk)(createStore)(reducer) export default store

actions.js

import * as TYPES from './actions_type' export default { add() { return {type: TYPES.ADD} }, minus() { return {type: TYPES.MINUS} }, //正常的actions必須是一個純對象,不能是函數{type:'add'}
 thunkAdd() { return function (dispatch, getState) { setTimeout(function() { dispatch({type: TYPES.ADD}) },1000) } } }
<button onClick={boundActions.thunkAdd}>thunkAdd</button>

點擊按鈕觸發thunkAdd這個actions,它是一個函數actions,所以在調用之前進行判斷,thunk這個中間件就是用來給store派發函數類型的actions的

 4.5.3 Promise 中間件

Action Creator 返回一個 Promise 對象。

function promise ({dispatch, getState}) { return function (next) { return function (action) { if(typeof action.then === 'function') { action.then(dispatch) //action.then( result => dispatch(dispatch)) 
            }else { next(action) } } } } function applyMiddleware(middleware) { //middleware = logger
    return function(createStore) { return function (reducer) { let store = createStore(reducer) // 返回的是原始的未修改鍋的store
 let dispatch middleware = middleware({ //logger執行 需要傳參getState 和 dispatch 此時的 middleware = function(next)
 getState: store.getState, dispatch: action => dispatch(action) //指向改寫后的新的dispatch 不能是store.dispatch
 }) dispatch = middleware(store.dispatch) //執行上面返回的middleware ,store.dispatch 代表next
            return { ...store, dispatch } } } } let store = applyMiddleware(promise)(createStore)(reducer) export default store

actions.js

import * as TYPES from './actions_type' export default { add() { return {type: TYPES.ADD} }, minus() { return {type: TYPES.MINUS} }, //正常的actions必須是一個純對象,不能是函數{type:'add'}
 thunkAdd() { return function (dispatch, getState) { setTimeout(function() { dispatch({type: TYPES.ADD}) },1000) } }, promiseAdd(){ return new Promise(function (resolve) { setTimeout(function () { resolve({type: TYPES.ADD}) },1000) }) } }
<button onClick={boundActions.promiseAdd}>promiseAdd</button>

 Action 本身是一個 Promise,它 resolve 以后的值應該是一個 Action 對象,會被dispatch方法送出(action.then(dispatch)

 4.5.4 級聯中間件

 

上面我們調用的中間件都是單個調用,傳進applyMiddleware的參數也是單個的,但是我們要想一次調用多個中間件,那么傳到applymiddware的參數就是個數組,這個時候就需要級聯處理,讓他們一次執行。

applyMiddleware.js

function applyMiddleware(...middlewares  ) { //middleware = logger
    return function(createStore) { return function (reducer) { let store = createStore(reducer) // 返回的是原始的未修改鍋的store
            let middlewareAPI = { getState: store.getState, dispatch: action => dispatch(action) //指向改寫后的新的dispatch 不能是store.dispatch  } let dispatch chain= middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) // dispatch = middleware(store.dispatch) //執行上面返回的middleware ,store.dispatch 代表next
            return { ...store, dispatch } } } } let store = applyMiddleware(promise,thunk, logger)(createStore)(reducer)

compose.js

function compose(...fns) { return fns.reduce((a,b) => function(...args) { return a(b(...args)) }) }

 上面代碼中,所有中間件被放進了一個數組chain,然后嵌套執行,最后執行store.dispatch。可以看到,中間件內部(middlewareAPI)可以拿到getStatedispatch這兩個方法。

 compose方法就是合並改造后的dispatch方法,。最終的結果就是  retrurn promise(thunk(logger(dispatch)))

 

 相關優秀文章推薦:

阮一峰:http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_two_async_operations.html

深入理解redux中間件:https://www.jianshu.com/p/ae7b5a2f78ae

redux如何實現組件之間數據共享:https://segmentfault.com/a/1190000009403046

https://www.cnblogs.com/wy1935/p/7109701.html

 Redux解決了什么問題:https://www.ucloud.cn/yun/104048.html

https://www.cnblogs.com/rudylemon/p/redux.html

https://zhuanlan.zhihu.com/p/50247513

https://www.redux.org.cn/docs/recipes/reducers/UsingCombineReducers.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM