zoukankan      html  css  js  c++  java
  • 深入浅出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

    不积跬步无以至千里
  • 相关阅读:
    php 5.3新特性
    php:// — 访问各个输入/输出流(I/O streams)
    php 二维数组排序
    js 面向对象式编程
    jQuery 源码学习笔记
    c++ 指针(二)
    c++ 指针(一)
    visual studio 2012 的制作ActiveX、打包和发布
    用linqPad帮助你快速学习LINQ
    Caliburn.Micro学习笔记(五)----协同IResult
  • 原文地址:https://www.cnblogs.com/lyt0207/p/12766402.html
Copyright © 2011-2022 走看看