深入浅出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

 

posted @ 2020-04-28 19:11  leahtao  阅读(1829)  评论(0编辑  收藏  举报