Redux的一些總結

舞動乾坤發表於2019-02-27

Redux 學習筆記

淺說Flux開發中,簡單介紹了Flux及其開發方式。Flux可以說是一個框架,其有本身的 Dispatcher 介面供開發者;也可以說是一種資料流單向控制的架構設計,圍繞單向資料流的核心,其定義了一套行為規範,如下圖:

Redux的一些總結

Redux的設計就繼承了Flux的架構,並將其完善,提供了多個API供開發者呼叫。藉著react-redux,可以很好的與React結合,開發元件化程度極高的現代Web應用。本文是筆者近半年使用react+redux組合的一些總結,不當之處,敬請諒解。

Action

Action是資料從應用傳遞到 store/state 的載體,也是開啟一次完成資料流的開始。

以新增一個todo的Action為例:

{
    type:'add_todo',
    data:'我要去跑步'
}
複製程式碼

這樣就定義了一個新增一條todo的Action,然後就能通過某個行為去觸發這個Action,由這個Action攜帶的資料(data)去更新store(state/reducer):

store.dispatch({
    type:'add_todo',
    data:'your data'
})
複製程式碼

type 是一個常量,Action必備一個欄位,用於標識該Action的型別。在專案初期,這樣定義Action也能愉快的擼碼,但是隨著專案的複雜度增加,這種方式會讓程式碼顯得冗餘,因為如果有多個行為觸發同一個Action,則這個Action要寫多次;同時,也會造成程式碼結構不清晰。因而,得更改建立Action的方式:

const ADD_TODO = 'add_todo';

let addTodo = (data='default data') => {
    return {
        type: ADD_TODO,
        data: data
    }
}

//觸發action
store.dispatch(addTodo());
複製程式碼

更改之後,程式碼清晰多了,如果有多個行為觸發同一個Action,只要呼叫一下函式 addTodo 就行,並將Action要攜帶的資料傳遞給該函式。類似 addTodo 這樣的函式,稱之為 Action Creator。Action Creator 的唯一功能就是返回一個Action供 dispatch 進行呼叫。

但是,這樣的Action Creator 返回的Action 並不是一個標準的Action。在Flux的架構中,一個Action要符合 FSA(Flux Standard Action) 規範,需要滿足如下條件:

  • 是一個純文字物件
  • 只具備 typepayloaderrormeta 中的一個或者多個屬性。type 欄位不可預設,其它欄位可預設
  • 若 Action 報錯,error 欄位不可預設,切必須為 true

payload 是一個物件,用作Action攜帶資料的載體。所以,上述的寫法可以更改為:

let addTodo = (data='default data') => {
    return {
        type: ADD_TODO,
        payload: {
            data
        }
    }
}
複製程式碼

在 redux 全家桶中,可以利用 redux-actions 來建立符合 FSA 規範的Action:

import {creatAction} from 'redux-actions';

let addTodo = creatAction(ADD_TODO)
//same as
let addTodo = creatAction(ADD_TODO,data=>data)
複製程式碼

可以採用如下一個簡單的方式檢驗一個Action是否符合FSA標準:

let isFSA = Object.keys(action).every((item)=>{
   return  ['payload','type','error','meta'].indexOf(item) >  -1
})
複製程式碼

中介軟體

在我看來,Redux提高了兩個非常重要的功能,一是 Reducer 拆分,二是中介軟體。Reducer 拆分可以使元件獲取其最小屬性(state),而不需要整個Store。中介軟體則可以在 Action Creator 返回最終可供 dispatch 呼叫的 action 之前處理各種事情,如非同步API呼叫、日誌記錄等,是擴充套件 Redux 功能的一種推薦方式。

Redux 提供了 applyMiddleware(...middlewares) 來將中介軟體應用到 createStore。applyMiddleware 會返回一個函式,該函式接收原來的 creatStore 作為引數,返回一個應用了 middlewares 的增強後的 creatStore。

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    //接收createStore引數
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    //傳遞給中介軟體的引數
    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

    //註冊中介軟體呼叫鏈
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    //返回經middlewares增強後的createStore
    return {
      ...store,
      dispatch
    }
  }
}
複製程式碼

建立 store 的方式也會因是否使用中介軟體而略有區別。未應用中間價之前,建立 store 的方式如下:

import {createStore} from 'redux';
import reducers from './reducers/index';

export let store = createStore(reducers);
複製程式碼

應用中間價之後,建立 store 的方式如下:

import {createStore,applyMiddleware} from 'redux';
import reducers from './reducers/index';

let createStoreWithMiddleware = applyMiddleware(...middleware)(createStore);
export let store = createStoreWithMiddleware(reducers);
複製程式碼

那麼怎麼自定義一箇中介軟體呢?

根據 redux 文件,中介軟體的簽名如下:

({ getState, dispatch }) => next => action
複製程式碼

根據上文的 applyMiddleware 原始碼,每個中介軟體接收 getState & dispatch 作為引數,並返回一個函式,該函式會被傳入下一個中介軟體的 dispatch 方法,並返回一個接收 action 的新函式。

以一個列印 dispatch action 前後的 state 為例,建立一箇中介軟體示例:

export default function({getState,dispatch}) {
    return (next) => (action) => {
        console.log('pre state', getState());
        // 呼叫 middleware 鏈中下一個 middleware 的 dispatch。
        next(action);
        console.log('after dispatch', getState());
    }
}
複製程式碼

在建立 store 的檔案中呼叫該中介軟體:

import {createStore,applyMiddleware} from 'redux';

import reducers from './reducers/index';
import log from '../lib/log';

//export let store = createStore(reducers);

//應用中介軟體log
let createStoreWithLog = applyMiddleware(log)(createStore);
export let store = createStoreWithLog(reducers);
複製程式碼

可以在控制檯看到輸出:

Redux的一些總結

可以對 store 應用多箇中介軟體:

import log from '../lib/log';
import log2 from '../lib/log2';

let createStoreWithLog = applyMiddleware(log,log2)(createStore);
export let store = createStoreWithLog(reducers);
複製程式碼

log2 也是一個簡單的輸出:

export default function({getState,dispatch}) {
    return (next) => (action) => {
        console.log('我是第二個中介軟體1');
        next(action);
        console.log('我是第二個中介軟體2');
    }
}
複製程式碼

看控制檯的輸出:

Redux的一些總結

應用多箇中介軟體時,中介軟體呼叫鏈中任何一個缺少 next(action) 的呼叫,都會導致 action 執行失敗

非同步

Redux 本身不處理非同步行為,需要依賴中介軟體。結合 redux-actions 使用,Redux 有兩個推薦的非同步中介軟體:

兩個中介軟體的原始碼都是非常簡單的,redux-thunk 的原始碼如下:

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;
複製程式碼

從原始碼可知,action creator 需要返回一個函式給 redux-thunk 進行呼叫,示例如下:

export let addTodoWithThunk = (val) => async (dispatch, getState)=>{
    //請求之前的一些處理

    let value = await Promise.resolve(val + ' thunk');
    dispatch({
        type:CONSTANT.ADD_TO_DO_THUNK,
        payload:{
            value
        }
    });
};
複製程式碼

效果如下:

Redux的一些總結

這裡之所以不用 createAction,如前文所說,因為 createAction 會返回一個 FSA 規範的 action,該 action 會是一個物件,而不是一個 function:

{
    type: "add_to_do_thunk",
    payload: function(){}
}
複製程式碼

如果要使用 createAction,則要自定義一個非同步中介軟體。

export let addTodoWithCustom = createAction(CONSTANT.ADD_TO_DO_CUSTOM, (val) => async (dispatch, getState)=>{
    let value = await Promise.resolve(val + ' custom');
    return {
        value
    };
});
複製程式碼

在經過中介軟體處理時,先判斷 action.payload 是否是一個函式,是則執行函式,否則交給 next 處理:

if(typeof action.payload === 'function'){
    let res = action.payload(dispatch, getState);
} else {
    next(action);
}
複製程式碼

而 async 函式返回一個 Promise,因而需要作進一步處理:

res.then(
    (result) => {
        dispatch({...action, payload: result});
    },
    (error) => {
        dispatch({...action, payload: error, error: true});
    }
);
複製程式碼

這樣就自定義了一個非同步中介軟體,效果如下:

Redux的一些總結

當然,我們可以對函式執行後的結果是否是Promise作一個判斷:

function isPromise (val) {
    return val && typeof val.then === 'function';
}

//對執行結果是否是Promise
if (isPromise(res)){
    //處理
} else {
    dispatch({...action, payload: res});
}
複製程式碼

那麼,怎麼利用 redux-promise 呢?redux-promise 是能處理符合 FSA 規範的 action 的,其對非同步處理的關鍵原始碼如下:

action.payload.then(
    result => dispatch({ ...action, payload: result }),
    error => {
        dispatch({ ...action, payload: error, error: true });
        return Promise.reject(error);
    }
)
複製程式碼

因而,返回的 payload 不再是一個函式,而是一個 Promise。而 async 函式執行後就是返回一個 Promise,所以,讓上文定義的 async 函式自執行一次就可以:

export let addTodoWithPromise = createAction(CONSTANT.ADD_TO_DO_PROMISE, (val) =>
    (async (dispatch, getState)=>{
        let value = await Promise.resolve(val + ' promise');
        return {
            value
        };
    })()
);
複製程式碼

結果如下圖:

Redux的一些總結

示例原始碼:redux-demo





元件拆分

關於Redux的一些總結(一):Action & 中介軟體 & 非同步 一文中,有提到可以根據 reducer 對元件進行拆分,而不必將 state 中的資料原封不動地傳入元件,可以根據 state 中的資料,動態地輸出元件需要的(最小)屬性。

在常規的元件開發方式中,元件自身的資料和狀態是耦合的,這種方式雖能簡化開發流程,在短期內能提高開發效率,但只適用於小型且複雜度不高的SPA 應用開發,而對於複雜的 SPA 應用來說,這種開發方式不具備良好的擴充套件性。以開發一個評論元件 Comment 為例,常規的開發方式如下:

class CommentList extends Component {
    constructor(){
        super();
        this.state = {commnets: []}
    }

    componentDidMount(){
        $.ajax({
            url:'/my-comments.json',
            dataType:'json',
            success:function(data){
                this.setState({comments:data});
            }.bind(this)
        })
    }

    render(){
        return <ul>{this.state.comments.map(renderComment)}</ul>;
    }

    renderComment({body,author}){
        return <li>{body}-{author}</li>;
    }
}
複製程式碼

隨著應用的複雜度和元件複雜度的雙重增加,現有的元件開發方式已經無法滿足需求,它會讓元件變得不可控制和難以維護,極大增加後續功能擴充套件的難度。並且由於元件的狀態和資料的高度耦合,這種元件是無法複用的,無法抽離出通用的業務無關性元件,這勢必也會增加額外的工作量和開發時間。

在元件的開發過程中,從元件的職責角度上,將元件分為 容器類元件(Container Component) 和 展示類元件(Presentational Component)。前者主要從 state 獲取元件需要的(最小)屬性,後者主要負責介面渲染和自身的狀態(state)控制,為容器元件提供樣式。

按照上述的概念,Comment應該有兩部分組成:CommentListContainer和CommentList。首先定義一個容器類元件(Container Component):

//CommentListContainer
class CommentListContainer extends Component {
    constructor(){
        super();
        this.state = {commnets: []}
    }

    componentDidMount(){
        $.ajax({
            url:'/my-comments.json',
            dataType:'json',
            success:function(data){
                this.setState({comments:data});
            }.bind(this)
        })
    }

    render(){
        return <CommnetList comments={this.state.comments}/>;
    }
}
複製程式碼

容器元件CommentListContainer獲取到資料之後,通過props傳遞給子元件CommentList進行介面渲染。CommentList是一個展示類元件:

//CommentList
class CommentList extends Component {
    constructor(props){
        super(props);
        this.state = {commnets: []}
    }


    render(){
        return <ul>{this.props.comments.map(renderComment)}</ul>;
    }

    renderComment({body,author}){
        return <li>{body}-{author}</li>;
    }
}
複製程式碼

將Comment元件拆分後,元件的自身狀態和非同步資料被分離,介面樣式由展示類元件提供。這樣,對於後續的業務資料變化需求,只需要更改容器類元件或者增加新的展示類業務元件,極大提高了元件的擴充套件性。

Container Component

容器類元件主要功能是獲取 state 和提供 action,渲染各個子元件。各個子元件或是一個展示類元件,或是一個容器元件,其職責具體如下:

  • 獲取 state 資料;
  • 渲染內部的子元件;
  • 無樣式;
  • 作為容器,巢狀其它的容器類元件或展示類元件;
  • 為展示類元件提供 action,並提供callback給其子元件。

Presentational Component

展示類元件自身的資料來自於父元件(容器類元件或展示類元件),元件自身提供樣式和管理元件狀態。展示類元件是狀態化的,其主要職責如下:

  • 接受props傳遞的資料;
  • 接受props傳遞的callback;
  • 定義style;
  • 使用其它的展示類元件;
  • 可以有自己的狀態(state)。

聯結器:connect

react-redux 為 React 元件和 Redux 提供的 state 提供了連線。當然可以直接在 React 中使用 Redux:在最外層容器元件中初始化 store,然後將 state 上的屬性作為 props 層層傳遞下去。

class App extends Component{

  componentWillMount(){
    store.subscribe((state)=>this.setState(state))
  }

  render(){

    return <Comp state={this.state}
                 onIncrease={()=>store.dispatch(actions.increase())}
                 onDecrease={()=>store.dispatch(actions.decrease())}/>
  }
}
複製程式碼

但這並不是所推薦的方式,相比上述的方式,更好的一個寫法是結合 react-redux。

首先在最外層容器中,把所有內容包裹在 Provider 元件中,將之前建立的 store 作為 prop 傳給 Provider。

const App = () => {
  return (
    <Provider store={store}>
      <Comp/>
    </Provider>
  )
};
複製程式碼

Provider 內的任何一個元件(比如這裡的 Comp),如果需要使用 state 中的資料,就必須是「被 connect 過的」元件——使用 connect 方法對「你編寫的元件(MyComp)」進行包裝後的產物。

class MyComp extends Component {
  // content...
}

const Comp = connect(...args)(MyComp);
複製程式碼

connect 會返回一個與 store 連線後的新元件。那麼,我們就可以傳一個 Presentational Component 給 connect,讓 connect 返回一個與 store 連線後的 Container Component。

connect 接受四個引數,返回一個函式:

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}){
        //code

        return function wrapWithConnect(WrappedComponent){
            //other code
            ....
            //merge props
            function computeMergedProps(stateProps, dispatchProps, parentProps) {
                    const mergedProps = finalMergeProps(stateProps, dispatchProps, parentProps)
                    if (process.env.NODE_ENV !== 'production') {
                           checkStateShape(mergedProps, 'mergeProps')
                    }
                   return mergedProps
        }

    ....

    render(){

        //other code
        ....

        if (withRef) {
              this.renderedElement = createElement(
WrappedComponent, {
                        ...this.mergedProps,
                       ref: 'wrappedInstance'
              })
        } else {
              this.renderedElement = createElement(
                WrappedComponent,
                this.mergedProps
               )
        }

             return this.renderedElement
      }

        }
}
複製程式碼

wrapWithConnect 接受一個元件作為引數,在 render 會呼叫 React 的 createElement 基於傳入的元件和新的 props 返回一個新的元件。

以 connect 的方式來改寫Comment元件:

//CommentListContainer
import getCommentList '../actions/index'
import CommentList '../comment-list.js';

function mapStateToProps(state){
    return {
        comment: state.comment,
        other: state.other
    }
}

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

export default connect(mapStateToProps,mapDispatchToProps)(CommentList);
複製程式碼

在Comment元件中,CommentListContainer 只作為一個聯結器作用,連線
CommentList 和 state:

//CommentList
class CommentList extends Component {
    constructor(props){
        super(props);
    }

    componentWillMount(){
        //獲取資料
        this.props.getCommentList();
    }

    render(){
        let {comment}  = this.props;

        if(comment.fetching){
            //正在載入
            return <Loading />
        }

        //如果對CommentList item的操作比較複雜,也可以將item作為一個獨立元件
        return <ul>{this.props.comments.map(renderComment)}</ul>;
    }

    renderComment({body,author}){
        return <li>{body}-{author}</li>;
    }
}
複製程式碼

關於 connect 比較詳細的解釋可以參考:React 實踐心得:react-redux 之 connect 方法詳解





Redux:自問自答

  前段時間看了Redux的原始碼,寫了一篇關於Redux的原始碼分析: Redux:百行程式碼千行文件,沒有看的小夥伴可以看一下,整篇文章主要是對Redux執行的原理進行了大致的解析,但是其實有很多內容並沒有明確地深究為什麼要這麼做?本篇文章的內容主要就是我自己提出一些問題,然後試著去回答這個問題,再次做個廣告,歡迎大家關注我的掘金賬號和我的部落格。   

為什麼createStore中既存在currentListeners也存在nextListeners?

  看過原始碼的同學應該瞭解,createStore函式為了儲存store的訂閱者,不僅儲存了當前的訂閱者currentListeners而且也儲存了nextListenerscreateStore中有一個內部函式ensureCanMutateNextListeners:   

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
}複製程式碼

  這個函式實質的作用是確保可以改變nextListeners,如果nextListenerscurrentListeners一致的話,將currentListeners做一個拷貝賦值給nextListeners,然後所有的操作都會集中在nextListeners,比如我們看訂閱的函式subscribe:

function subscribe(listener) {
// ......
    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
        // ......
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
}複製程式碼

  我們發現訂閱和解除訂閱都是在nextListeners做的操作,然後每次dispatch一個action都會做如下的操作:

function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 相當於currentListeners = nextListeners const listeners = currentListeners
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
  }複製程式碼

  我們發現在dispatch中做了const listeners = currentListeners = nextListeners,相當於更新了當前currentListenersnextListeners,然後通知訂閱者,到這裡我們不禁要問為什麼要存在這個nextListeners?
  
  其實程式碼中的註釋也是做了相關的解釋:

The subscriptions are snapshotted just before every dispatch() call.If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on the dispatch() that is currently in progress.However, the next dispatch() call, whether nested or not, will use a more recent snapshot of the subscription list. 

  來讓我這個六級沒過的渣渣翻譯一下: 訂閱者(subscriptions)在每次dispatch()呼叫之前都是一份快照(snapshotted)。如果你在listener被呼叫期間,進行訂閱或者退訂,在本次的dispatch()過程中是不會生效的,然而在下一次的dispatch()呼叫中,無論dispatch是否是巢狀呼叫的,都將使用最近一次的快照訂閱者列表。用圖表示的效果如下:
  

Redux的一些總結  

  

  我們從這個圖中可以看見,如果不存在這個nextListeners這份快照的話,因為dispatch導致的store的改變,從而進一步通知訂閱者,如果在通知訂閱者的過程中發生了其他的訂閱(subscribe)和退訂(unsubscribe),那肯定會發生錯誤或者不確定性。例如:比如在通知訂閱的過程中,如果發生了退訂,那就既有可能成功退訂(在通知之前就執行了nextListeners.splice(index, 1))或者沒有成功退訂(在已經通知了之後才執行了nextListeners.splice(index, 1)),這當然是不行的。因為nextListeners的存在所以通知訂閱者的行為是明確的,訂閱和退訂是不會影響到本次訂閱者通知的過程。

  這都沒有問題,可是存在一個問題,JavaScript不是單執行緒的嗎?怎麼會出現上述所說的場景呢?百思不得其解的情況下,去Redux專案下開了一個issue,得到了維護者的回答:

Redux的一些總結  

  得了,我們再來看看測試相關的程式碼吧。看完之後我瞭解到了。的確,因為JavaScript是單執行緒語言,不可能出現出現想上述所說的多執行緒場景,但是我忽略了一點,執行訂閱者函式時,在這個回撥函式中可以執行退訂或者訂閱事件。例如:

const store = createStore(reducers.todos)
const unsubscribe1 = store.subscribe(() => {
    const unsubscribe2 = store.subscribe(()=>{})
})複製程式碼

  這不就實現了在通知listener的過程中混入訂閱subscribe與退訂unsubscribe嗎?   

為什麼Reducer中不能進行dispatch操作?

  我們知道在reducer函式中是不能執行dispatch操作的。一方面,reducer作為計算下一次state的純函式是不應該承擔執行dispatch這樣的操作。另一方面,即使你嘗試著在reducer中執行dispatch,也並不會成功,並且會得到"Reducers may not dispatch actions."的提示。因為在dispatch函式就做了相關的限制:   

function dispatch(action) {
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    //...notice listener
}複製程式碼

  在執行dispatch時就會將標誌位isDispatching置為true。然後如果在currentReducer(currentState, action)執行的過程中由執行了dispatch,那麼就會丟擲錯誤('Reducers may not dispatch actions.')。之所以做如此的限制,是因為在dispatch中會引起reducer的執行,如果此時reducer中又執行了dispatch,這樣就落入了一個死迴圈,所以就要避免reducer中執行dispatch

為什麼applyMiddleware中middlewareAPI中的dispathc要用閉包包裹?

  關於Redux的中介軟體之前我寫過一篇相關的文章Redux:Middleware你咋就這麼難,沒有看過的同學可以瞭解一下,其實文章中也有一個地方沒有明確的解釋,當時初學不是很理解,現在來解釋一下:   

export default function applyMiddleware(...middlewares) {            
    return (next)  => 
        (reducer, initialState) => {

              var store = next(reducer, initialState);
              var dispatch = store.dispatch;
              var chain = [];

              var middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
              };

              chain = middlewares.map(middleware =>
                            middleware(middlewareAPI));
              dispatch = compose(...chain, store.dispatch);
              return {
                ...store,
                dispatch
              };
           };
}複製程式碼

  這個問題的就是為什麼middlewareAPI中的dispathc要用閉包包裹,而不是直接傳入呢?首先用一幅圖來解釋一下中介軟體:
  
  

Redux的一些總結

  

  如上圖所示,中介軟體的執行過程非常類似於洋蔥圈(Onion Rings),假設我們在函式applyMiddleware中傳入中介軟體的順序分別是mid1、mid2、mid3。而中介軟體函式的結構類似於:

export default function createMiddleware({ getState }) {
    return (next) => 
        (action) => {
            //before
            //......
            next(action)
            //after
            //......
        };
}複製程式碼

  那麼中介軟體函式內部程式碼執行次序分別是:
  

Redux的一些總結

  但是如果在中介軟體函式中呼叫了dispatch(用mid3-before中為例),執行的次序就變成了:

Redux的一些總結  

  所以給中介軟體函式傳入的middlewareAPIdispatch函式是經過applyMiddleware改造過的dispatch,而不是redux原生的store.dispatch。所以我們通過一個閉包包裹dispatch:

(action) => dispatch(action)複製程式碼

  這樣我們在後面給dispatch賦值為dispatch = compose(...chain, store.dispatch);,這樣只要 dispatch 更新了,middlewareAPI 中的 dispatch 應用也會發生變化。如果我們寫成:

var middlewareAPI = {
    getState: store.getState,
    dispatch: dispatch
};複製程式碼

那中介軟體函式中接受到的dispatch永遠只能是最開始的redux中的dispatch

  最後,如果大家在閱讀Redux原始碼時還有別的疑惑和感受,歡迎大家在評論區相互交流,討論和學習。


相關文章