Redux 原理分析

119 阅读15分钟

在⼀切开始之前,我们⾸先要回答⼀个问题:为什么我们需要redux,redux为我们解决了什么问题?只有回答了这个 问题,我们才能把握redux的设计思路。

React作为⼀个组件化开发框架,组件之间存在⼤量通信,有时这些通信跨越多个组件,或者多个组件之间共享⼀套数 据,简单的⽗⼦组件间传值不能满⾜我们的需求,⾃然⽽然地,我们需要有⼀个地⽅存取和操作这些公共状态。⽽ redux就为我们提供了⼀种管理公共状态的⽅案 ,我们后续的设计实现也将围绕这个需求来展开。

我们思考⼀下如何管理公共状态:既然是公共状态,那么就直接把公共状态提取出来好了。我们创建⼀个store.js⽂ 件,然后直接在⾥边存放公共的state,其他组件只要引⼊这个store就可以存取共⽤状态了。

const state = {         
    count: 0 
}

我们在store⾥存放⼀个公共状态count,组件在import了store后就可以操作这个count。这是最直接的store,当然我 们的store肯定不能这么设计,原因主要是两点:

1.容易误操作

⽐如说,有⼈⼀个不⼩⼼把store赋值了{},清空了store,或者误修改了其他组件的数据,那显然不太安全,出错了也 很难排查,因此我们需要有条件地操作store,防⽌使⽤者直接修改store的数据。

state = "for the horde!!" // 哦豁

2.可读性很差

JS是⼀⻔极其依赖语义化的语⾔,试想如果在代码中不经注释直接修改了公⽤的state,以后其他⼈维护代码得多懵 逼,为了搞清楚修改state的含义还得根据上下⽂推断,所以我们最好是给每个操作起个名字。

我们重新思考⼀下如何设计这个公共状态管理器,根据我们上⾯的分析,我们希望 公共状态既能够被全局访问到,⼜是私有的不能被 直接修改 ,思考⼀下, 闭包 是不是就就正好符合这两条要求,因此我们会把公共状态设计成闭包。

既然我们要存取状态,那么肯定要有 gettersetter ,此外当状态发⽣改变时,我们得进⾏⼴播,通知组件状态发⽣ 了变更。这不就和redux的三个API: getState、dispatch、subscribe 对应上了吗。我们⽤⼏句代码勾勒出store的⼤致形 状:

export const createStore = () => {    
    let currentState = {}       // 公共状态         
    function getState() {}      // getter         
    function dispatch() {}      // setter         
    function subscribe() {}     // 发布订阅         
    return { getState, dispatch, subscribe } 
}

1. getState实现

getState() 的实现⾮常简单,返回当前状态即可:

function getState() {          return currentState     }

2. dispatch实现

但是 dispatch() 的实现我们得思考⼀下,经过上⾯的分析,我们的⽬标是 有条件地、具名地 修改store的数据,那么我们要 如何实现这两点呢?我们已经知道,在使⽤dispatch的时候,我们会给dispatch()传⼊⼀个action对象,这个对象包括 我们要修改的state以及这个操作的名字(actionType),根据type的不同,store会修改对应的state。我们这⾥也沿⽤ 这种设计:

function dispatch(action) {          
    switch (action.type) {               
        case 'plus':                
        currentState = {                      
            ...state,                     
            count: currentState.count + 1                
        }          
    }     
}

我们把对actionType的判断写在了dispatch中,这样显得很臃肿,也很笨拙,于是我们想到把这部分修改state的规则 抽离出来放到外⾯,这就是我们熟悉的reducer。我们修改⼀下代码,让reducer从外部传⼊:

import { reducer } from './reducer' 
export const createStore = (reducer) => {        
     let currentState = {}         
     function getState() {                
         return currentState        
     }        
     function dispatch(action) {                 
         currentState = reducer(currentState, action)      
     }         
     function subscribe() {}        
     return { getState, dispatch, subscribe } 
}

然后我们创建⼀个reducer.js⽂件,写我们的reducer

//reducer.js 
const initialState = {         
    count: 0 
} 
export function reducer(state = initialState, action) {         
    switch(action.type) {               
        case 'plus':                 
            return {                         
                ...state,                                 
                count: state.count + 1                 
            }               
        case 'subtract':                 
            return {                         
                ...state,                         
                count: state.count - 1                 
            }               
        default:                 
            return initialState         
    } 
}

代码写到这⾥,我们可以验证⼀下getState和dispatch:

//store.js 
import { reducer } from './reducer' 
export const createStore = (reducer) => {         
    let currentState = {}             
    function getState() {                         
        return currentState             
    }             

    function dispatch(action) {                         
        currentState = reducer(currentState, action)       
    }             

    function subscribe() {}             
    return { getState, subscribe, dispatch } 
} 
 
const store = createStore(reducer)  //创建store 
store.dispatch({ type: 'plus' })    //执⾏加法操作,给count加1 
console.log(store.getState())       //获取state

运⾏代码,我们会发现,打印得到的state是:{ count: NaN },这是由于store⾥初始数据为空,state.count + 1实际 上是underfind+1,输出了NaN,所以我们得先进⾏store数据初始化,我们在执⾏dispatch({ type: 'plus' })之前先进⾏⼀次初始化的dispatch,这个dispatch的actionType可以随便填,只要不和已有的type重复, 让reducer⾥的switch能⾛ 到default去初始化store 就⾏了:

import { reducer } from './reducer' 
export const createStore = (reducer) => {             
    let currentState = {}             
    function getState() {                         
        return currentState             
    }             
    
    function dispatch(action) {                         
        currentState = reducer(currentState, action)             
    }       
    
    function subscribe() {}         
    
    dispatch({ type: '@@REDUX_INIT' })  //初始化store数据             
    return { getState, subscribe, dispatch } 
} 
 
const store = createStore(reducer)      //创建store 
store.dispatch({ type: 'plus' })        //执⾏加法操作,给count加1 
console.log(store.getState())           //获取state

运⾏代码,我们就能打印到的正确的state:{ count: 1 }

image.png

3.subscribe实现

尽管我们已经能够存取公⽤state,但store的变化并不会直接引起视图的更新,我们需要监听store的变化,这⾥我们 应⽤⼀个设计模式——观察者模式,观察者模式被⼴泛运⽤于监听事件实现。

所谓观察者模式,概念也很简单: 观察者监听被观察者的变化,被观察者发⽣改变时,通知所有的观察者

我们每次dispatch,都进⾏⼴播,通知组件store的状态发⽣了变更。

import { reducer } from './reducer'
export const createStore = (reducer) => {             
    let currentState = {}             
    let observers = []             //观察者队列   
    
    function getState() {                         
        return currentState             
    }             
    function dispatch(action) {                         
        currentState = reducer(currentState, action)                         
        observers.forEach(fn => fn())             
    }             
    function subscribe(fn) {                         
        observers.push(fn)             
    }             
    dispatch({ type: '@@REDUX_INIT' })  //初始化store数据             
    return { getState, subscribe, dispatch } 
}

我们来试⼀下这个subscribe(这⾥就不创建组件再引⼊store再subscribe了,直接在store.js中模拟⼀下两个组件使⽤ subscribe订阅store变化):

import { reducer } from './reducer' 
export const createStore = (reducer) => {             
    let currentState = {}             
    let observers = []             //观察者队列             
    function getState() {                         
        return currentState             
    }             
    function dispatch(action) {                         
        currentState = reducer(currentState, action)                         
        observers.forEach(fn => fn())             
    }             
    function subscribe(fn) {                         
        observers.push(fn)             
    }                 
    dispatch({ type: '@@REDUX_INIT' })  //初始化store数据             
    return { getState, subscribe, dispatch } 
} 
 
const store = createStore(reducer)       //创建store 
store.subscribe(() => { console.log('组件1收到store的通知') }) 
store.subscribe(() => { console.log('组件2收到store的通知') }) 
store.dispatch({ type: 'plus' })         //执⾏dispatch,触发store的通知

控制台成功输出store.subscribe()传⼊的回调的执⾏结果:

image.png

到这⾥,⼀个简单的redux就已经完成,在redux真正的源码中还加⼊了⼊参校验等细节,但总体思路和上⾯的基本相 同。

我们已经可以在组件⾥引⼊store进⾏状态的存取以及订阅store变化,数⼀下,⼗六⾏代码。

image.png

尽管说我们已经实现了redux,但并不满⾜于此,我们在使⽤store时,需要在每个组件中引⼊store,然后getState, 然后dispatch,还有subscribe,代码⽐较冗余,我们需要合并⼀些重复操作,⽽其中⼀种简化合并的⽅案,就是我们 熟悉的react-redux

react-redux 的实现

react-redux提供 Providerconnect 两个API,

  1. Provider将store放进this.context⾥,省去了import这⼀步,
  2. connect将getState、dispatch合并进了this.props,并⾃动订阅更新,简化了另外三步。

下⾯我们来看⼀下如何实现这两个API:

1. Provider实现

我们先从⽐较简单的 Provider 开始实现,Provider是⼀个组件,接收store并放进全局的 context 对象,⾄于为什么要放 进context,后⾯我们实现connect的时候就会明⽩。

import React from 'react' 
import PropTypes from 'prop-types' 
export class Provider extends React.Component {       
    // 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法       
    static childContextTypes = {             
        store: PropTypes.object       
    }  
 
    // 实现getChildContext⽅法,返回context对象,也是固定写法       
    getChildContext() {             
        return { store: this.store }       
    }   
 
    constructor(props) {             
        super(props)             
        this.store = props.store       
    }   
 
    // 渲染被Provider包裹的组件       
    render() {             
        return this.props.children       
    } 
}

完成Provider后,我们就能在组件中通过this.context.store这样的形式取到store,不需要再单独import store。

2. connect实现

下⾯我们来思考⼀下如何实现 connect ,我们先回顾⼀下connect的使⽤⽅法:

connect(mapStateToProps, mapDispatchToProps)(App)

我们已经知道,connect接收mapStateToProps、mapDispatchToProps两个⽅法,然后返回⼀个⾼阶函数,这个⾼ 阶函数接收⼀个组件,返回⼀个⾼阶组件(其实就是给传⼊的组件增加⼀些属性和功能)connect根据传⼊的map,将 state和dispatch(action)挂载⼦组件的props上,我们直接放出connect的实现代码:

export function connect(mapStateToProps, mapDispatchToProps) {         
    return function(Component) {               
        class Connect extends React.Component {                     
            componentDidMount() {                           
                //从context获取store并订阅更新
                this.context.store.subscribe(this.handleStoreChange.bind(this));        
            }                    
            handleStoreChange() {                           
                // 触发更新                           
                // 触发的⽅法有多种,这⾥为了简洁起⻅,直接forceUpdate强制更新,读者也可以通过setState来触发⼦组件更新   
                this.forceUpdate()                     
            }                     
            render() {                          
                return (                                 
                    <Component                                       
                    // 传⼊该组件的props,需要由connect这个⾼阶组件原样传回原组件                                       { ...this.props }                                       
                    // 根据mapStateToProps把state挂到this.props上                                       
                    { ...mapStateToProps(this.context.store.getState()) } 
                    // 根据mapDispatchToProps把dispatch(action)挂到this.props上   
                    { ...mapDispatchToProps(this.context.store.dispatch) } 
                    />                               
                )                     
            }               
        }               

        //接收context的固定写法               
        Connect.contextTypes = {                     
            store: PropTypes.object               
        }               
        return Connect         
    } 
}

写完了connect的代码,我们有两点需要解释⼀下:

  1. Provider的意义:我们审视⼀下connect的代码,其实context不过是给connect提供了获取store的途径,我们在 connect中直接import store完全可以取代context。那么Provider存在的意义是什么,上⾯这个connect是⾃⼰写 的,当然可以直接import store,但react-redux的connect是封装的,对外只提供api,所以需要让Provider传⼊ store。

  2. connect中的装饰器模式:回顾⼀下connect的调⽤⽅式: connect(mapStateToProps, mapDispatchToProps)(App) 其实 connect完全可以把App跟着mapStateToProps⼀起传进去,看似没必要return⼀个函数再传⼊App,为什么reactredux要这样设计,react-redux作为⼀个被⼴泛使⽤的模块,其设计肯定有它的深意。

其实connect这种设计,是装饰器模式的实现,所谓装饰器模式,简单地说就是对类的⼀个包装,动态地拓展类的功 能。connect以及React中的⾼阶组件(HoC)都是这⼀模式的实现。除此之外,也有更直接的原因:这种设计能够兼 容ES7的 装饰器(Decorator) ,使得我们可以⽤@connect这样的⽅式来简化代码。

写完了react-redux,我们可以写个demo来测试⼀下:使⽤ create-react-app 创建⼀个项⽬,删掉⽆⽤的⽂件,并创建 store.js、reducer.js、react-redux.js来分别写我们redux和react-redux的代码,index.js是项⽬的⼊⼝⽂件,在App.js中我们简单的写⼀个计数器,点击按钮就派发⼀个dispatch,让store中的count加⼀,⻚⾯上显⽰这个count。 最后⽂件⽬录和代码如下:

image.png

// store.js 
export const createStore = (reducer) => {         
    let currentState = {}         
    let observers = []             //观察者队列         
    function getState() {                 
        return currentState         
    }         
    function dispatch(action) {                 
        currentState = reducer(currentState, action)                
        observers.forEach(fn => fn())         
    }         
    function subscribe(fn) {                 
        observers.push(fn)         
    }         
    dispatch({ type: '@@REDUX_INIT' }) //初始化store数据         
    return { getState, subscribe, dispatch } 
}
//reducer.js 
const initialState = {         
    count: 0 
} 
 
export function reducer(state = initialState, action) {         
    switch(action.type) {               
        case 'plus':                 
        return {                         
            ...state,                         
            count: state.count + 1                 
        }               
        case 'subtract':                 
        return {                         
            ...state,                         
            count: state.count - 1                 
        }               
        default:                 
        return initialState         
    } 
}
//react-redux.js 
import React from 'react' 
import PropTypes from 'prop-types' 
export class Provider extends React.Component {       
    // 需要声明静态属性childContextTypes来指定context对象的属性,是context的固定写法       
    static childContextTypes = {             
        store: PropTypes.object       
    } 
    
    // 实现getChildContext⽅法,返回context对象,也是固定写法       
    getChildContext() {             
        return { store: this.store }       
    }   
 
    constructor(props, context) {             
        super(props, context)             
        this.store = props.store       
    }   
 
    // 渲染被Provider包裹的组件       
    render() {             
        return this.props.children       
    } 
} 
 
export function connect(mapStateToProps, mapDispatchToProps) {         
    return function(Component) {           
        class Connect extends React.Component {                 
            componentDidMount() {          
                //从context获取store并订阅更新                       
                this.context.store.subscribe(this.handleStoreChange.bind(this));
            }                 
            handleStoreChange() {                       
                // 触发更新                       
                // 触发的⽅法有多种,这⾥为了简洁起⻅,直接forceUpdate强制更新,读者也可以通过setState来触发⼦组件更新                       
                this.forceUpdate()                 
            }                 
            render() {                       
                return (                             
                    <Component                                   
                    // 传⼊该组件的props,需要由connect这个⾼阶组件原样传回原组件                                   
                    { ...this.props }                                   
                    // 根据mapStateToProps把state挂到this.props上                                   
                    { ...mapStateToProps(this.context.store.getState()) }
                    // 根据mapDispatchToProps把dispatch(action)挂到this.props上    
                    { ...mapDispatchToProps(this.context.store.dispatch) }  
                    />                       
                )                 
            }           
        }       

        //接收context的固定写法           
        Connect.contextTypes = {                 
            store: PropTypes.object           
        }           
        return Connect         
    } 
}  
// index.js 
import React from 'react'; 
import ReactDOM from 'react-dom'; 
import './index.css'; 
import App from './App'; 
 
import { Provider } from './react-redux' 
import { createStore } from './store' 
import { reducer } from './reducer' 
 
ReactDOM.render(        
    <Provider store={createStore(reducer)}>                 
        <App />         
    </Provider>,          
    document.getElementById('root') 
)
// App.js 
import React from "react"; 
import { connect } from "./react-redux"; 
 
const addCountAction = {   type: "plus" }; 
 
const mapStateToProps = (state) => {   
    return {     
        count: state.count   
    }; 
}; 
 
const mapDispatchToProps = (dispatch) => {   
    return {     
        addCount: () => {       
            dispatch(addCountAction);     
        }   
    }; 
}; 
 
class App extends React.Component {   
    render() {     
        return (       
            <div className="App">         
                {this.props.count}         
                <button onClick={() => this.props.addCount()}>增加</button>       
            </div>     
        );   
    } 
} 
 
export default connect(mapStateToProps, mapDispatchToProps)(App);

运⾏项⽬,点击增加按钮,能够正确的计数,OK⼤成功,我们整个redux、react-redux的流程就⾛通了。

image.png

redux Middleware 的实现

上⾯redux和react-redux的实现都⽐较简单,下⾯我们来分析实现稍困难⼀些的redux中间件。所谓中间件,我们可以 理解为拦截器,⽤于对某些过程进⾏拦截和处理,且中间件之间能够串联使⽤。在redux中, 我们中间件拦截的是dispatch提交 到reducer这个过程,从⽽增强dispatch的功能

image.png

我们思考⼀下,如果我们想在每次dispatch之后,打印⼀下store的内容,我们会如何实现呢:

1. 在每次dispatch之后⼿动打印store的内容

store.dispatch({ type: 'plus' }) 
console.log('next state', store.getState())

这是最直接的⽅法,当然我们不可能在项⽬⾥每个dispatch后⾯都粘贴⼀段打印⽇志的代码,我们⾄少要把这部分功能 提取出来。

2. 封装dispatch

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

我们可以重新封装⼀个公⽤的新的dispatch⽅法,这样可以减少⼀部分重复的代码。不过每次使⽤这个新的dispatch都 得从外部引⼀下,还是⽐较⿇烦。

3. 替换dispatch

let next = store.dispatch 
store.dispatch = function dispatchAndLog(action) {       
    let result = next(action)       
    console.log('next state', store.getState())
    return result 
}

如果我们直接把dispatch给替换,这样每次使⽤的时候不就不需要再从外部引⽤⼀次了吗?对于单纯打印⽇志来说,这 样就⾜够了,但是如果我们还有⼀个监控dispatch错误的需求呢,我们固然可以在打印⽇志的代码后⾯加上捕获错误的 代码,但随着功能模块的增多,代码量会迅速膨胀,以后这个中间件就没法维护了,我们希望不同的功能是独⽴的 可拔插的模块。

4. 模块化

// 打印⽇志中间件 
function patchStoreToAddLogging(store) {         
    let next = store.dispatch   //此处也可以写成匿名函数         
    store.dispatch = function dispatchAndLog(action) {               
        let result = next(action)               
        console.log('next state', store.getState())               
        return result         
    } 
}   
 
// 监控错误中间件 
function patchStoreToAddCrashReporting(store) {         
    //这⾥取到的dispatch已经是被上⼀个中间件包装过的dispatch, 从⽽实现中间件串联         
    let next = store.dispatch         
    store.dispatch = function dispatchAndReportErrors(action) {                 
        try {                         
            return next(action)                 
        } catch (err) {                         
            console.error('捕获⼀个异常!', err)                         
            throw err                 
        }         
    } 
}

我们把不同功能的模块拆分成不同的⽅法,通过在⽅法内获取上⼀个中间件包装过的store.dispatch实现链式调⽤。然 后我们就能通过调⽤这些中间件⽅法,分别使⽤、组合这些中间件。

patchStoreToAddLogging(store) 
patchStoreToAddCrashReporting(store)

到这⾥我们基本实现了可组合、拔插的中间件,但我们仍然可以把代码再写好看⼀点。我们注意到,我们当前写的中间 件⽅法都是先获取dispatch,然后在⽅法内替换dispatch,这部分重复代码我们可以再稍微简化⼀下:我们不在⽅法内 替换dispatch,⽽是返回⼀个新的dispatch,然后让循环来进⾏每⼀步的替换。

5. applyMiddleware

改造⼀下中间件,使其返回新的dispatch⽽不是替换原dispatch。

function logger(store) {         
    let next = store.dispatch      
    
    // 我们之前的做法(在⽅法内直接替换dispatch):        
    // store.dispatch = function dispatchAndLog(action) {         
    //         ...         
    // }            
    return function dispatchAndLog(action) {                 
        let result = next(action)                 
        console.log('next state', store.getState())                 
        return result         
    } 
}

在Redux中增加⼀个辅助⽅法applyMiddleware,⽤于添加中间件。

function applyMiddleware(store, middlewares) {         
    middlewares = [ ...middlewares ]    //浅拷⻉数组, 避免下⾯reserve()影响原数组 
    middlewares.reverse()               //由于循环替换dispatch时,前⾯的中间件在最⾥层,因此需要翻转数组才能保证中间件的调⽤顺序           
    // 循环替换dispatch        
    middlewares.forEach(middleware =>               
        store.dispatch = middleware(store)         
    ) 
}

然后我们就能以这种形式增加中间件了:

applyMiddleware(store, [ logger, crashReporter ])

写到这⾥,我们可以简单地测试⼀下中间件。我创建了三个中间件,分别是logger1、thunk、logger2,其作⽤也很简 单,打印logger1 → 执⾏异步dispatch → 打印logger2,我们通过这个例⼦观察中间件的执⾏顺序。

// index.js 
import React from "react"; 
import ReactDOM from "react-dom"; 
import "./index.css"; 
import App from "./App"; 
import * as serviceWorker from "./serviceWorker"; 
 
import { Provider } from "./react-redux"; 
import { createStore } from "./store"; 
import { reducer } from "./reducer"; 
 
let store = createStore(reducer); 
 
function logger(store) {   
    let next = store.dispatch;   
    return (action) => {     
        console.log("logger1");     
        let result = next(action);     
        return result;   
    }; 
} 
 
function thunk(store) {   
    let next = store.dispatch;   
    return (action) => { 
        console.log("thunk");     
        return typeof action === "function" ? action(store.dispatch) : next(action);   
    }; 
} 
 
function logger2(store) {   
    let next = store.dispatch;   
    return (action) => {     
        console.log("logger2");     
        let result = next(action);     
        return result;   
    }; 
} 
 
function applyMiddleware(store, middlewares) {   
    middlewares = [...middlewares];   
    middlewares.reverse();   
    middlewares.forEach((middleware) => {     
        store.dispatch = middleware(store);   
    }); 
} 
 
applyMiddleware(store, [logger, thunk, logger2]); 
 
ReactDOM.render(   
    <Provider store={store}>     
        <App />   
    </Provider>,   
    document.getElementById("root") 
); 

image.png

6. 纯函数

之前的例⼦已经基本实现我们的需求,但我们还可以进⼀步改进,上⾯这个函数看起来仍然不够“纯”,函数在函数体内 修改了store⾃⾝的dispatch,产⽣了所谓的“副作⽤”,从函数式编程的规范出发,我们可以进⾏⼀些改造,借鉴reactredux的实现思路,我们可以把applyMiddleware作为⾼阶函数,⽤于增强store,⽽不是替换dispatch:

先对createStore进⾏⼀个⼩改造,传⼊heightener(即applyMiddleware),heightener接收并强化createStore。

// store.js 
export const createStore = (reducer, heightener) => {         
    // heightener是⼀个⾼阶函数,⽤于增强createStore         
    //如果存在heightener,则执⾏增强后的createStore         
    if (heightener) {                 
        return heightener(createStore)(reducer)         
    }             
    let currentState = {}         
    let observers = []             //观察者队列         
    function getState() {                 
        return currentState         
    }         
    function dispatch(action) {                 
        currentState = reducer(currentState, action);                 
        observers.forEach(fn => fn())         
    }         
    function subscribe(fn) {                 
        observers.push(fn)         
    }         
    dispatch({ type: '@@REDUX_INIT' })//初始化store数据         
    return { getState, subscribe, dispatch } 
}

中间件进⼀步柯⾥化,让next通过参数传⼊

const logger = store => next => action => {         
    console.log('log1')         
    let result = next(action)         
    return result 
} 
 
const thunk = store => next =>action => {     
    console.log('thunk')         
    const { dispatch, getState } = store         
    return typeof action === 'function' ? action(store.dispatch) : next(action) 
} 
 
const logger2 = store => next => action => {         
    console.log('log2')         
    let result = next(action)         
    return result 
}

改造applyMiddleware

const applyMiddleware = (...middlewares) => createStore => reducer => {         
const store = createStore(reducer)         
    let { getState, dispatch } = store         
    const params = {               
        getState,               
        dispatch: (action) => dispatch(action)               
        //解释⼀下这⾥为什么不直接 dispatch: dispatch               
        //因为直接使⽤dispatch会产⽣闭包,导致所有中间件都共享同⼀个dispatch,如果有中间件修改了dispatch或者进⾏异步dispatch就可能出错         
    }     
 
    const middlewareArr = middlewares.map(middleware => middleware(params))          
    dispatch = compose(...middlewareArr)(dispatch)         
    return { ...store, dispatch } 
} 
 
//compose这⼀步对应了middlewares.reverse(),是函数式编程⼀种常⻅的组合⽅法 
function compose(...fns) {     
    if (fns.length === 0) return arg => arg         
    if (fns.length === 1) return fns[0]     
    return fns.reduce((res, cur) =>(...args) => res(cur(...args))) 
}

代码应该不难看懂,在上⼀个例⼦的基础上,我们主要做了两个改造

  1. 使⽤compose⽅法取代了middlewares.reverse(),compose是函数式编程中常⽤的⼀种组合函数的⽅式, compose内部使⽤reduce巧妙地组合了中间件函数,使传⼊的中间件函数变成 (...arg) => mid1(mid2(mid3(...arg))) 这 种形式。
  2. 不直接替换dispatch,⽽是作为⾼阶函数增强createStore,最后return的是⼀个新的store。