Redux原理以及中间件的实现

939 阅读6分钟

redux的原理和中间件

redux是一个状态管理的容器,它主要是用来保证程序行为的一致性,通过单一数据源来修改状态,方便我们使用工具调试

基本使用

redux主要有如下几个步骤

  1. 创建一个store来存放数据 createStore
  2. store里面通过reducer来初始化state,并定义state的修改规则
  3. 在业务层通过dispatch 提交action来修改数据
  4. action提交到reducer函数中,根据传入的action的type, 返回新的state

创建store (src/store/index.js) 主要是用createStore

import { createStore } from 'redux'

const reducer = function (state = 0, { type, payload = 1 }) {
  switch (type) {
    case 'ADD': {
      return state + payload
    }
    case 'MINUS':
      return state - payload
    default:
      return state
  }
}
const store = createStore(reducer)
export default store

引入store, 修改App.js

  1. redux需要手动的更新,类组件有一个this.forceUpdate(), 函数组件需要手动的实现一个forceUpdate
import store from './store'
import { useEffect, useReducer } from 'react'
function App() {
  let state = store.getState()
  let [, forceUpdate] = useReducer(x => x+ 1, 0)
  useEffect(() => {
    let unListen = store.subscribe(() => {
      forceUpdate()
    })
    return () => {
      unListen && unListen()
    }
  }, [])
  return (
    <div className="App">
      Hello world
      <h5>{state}</h5>
      <button onClick={() => store.dispatch({ type: 'MINUS' })}>减一</button>
      <button onClick={() => store.dispatch({ type: 'ADD' })}>加一</button>
    </div>
  );
}

export default App;

主要就是一下几点

  1. createStore 创建store
  2. reducer 初始化,根据type和payload修改函数
  3. getState 获取状态值state
  4. dispatch 提交action通过reducer的规则进行更新
  5. subscribe 订阅,当state更新后通知, 记得组件卸载后取消订阅

实现Redux

其实redux本身没有做很多事情,只是单纯的接受reducer管理状态,所以实现redux本身很简单

export function createStore(reducer) {
  let state
  let listeners = []

  function getState() {
    return state
  }

  function dispatch(action) {
    state = reducer(state, action)
    listeners.forEach(listener => {
      listener && listener()
    })
    return action
  }

  function subscribe(listener) {
    listeners.push(listener)
    return () => {
      let index = listeners.findIndex(item => item === listener)
      listeners.splice(index, 1)
    }
  }
  // 触发初始化
  dispatch("init")
  return {
    getState,
    dispatch,
    subscribe,
  }
}

redux中间件

由于redux的功能单一,并且它自己对于dispatch的action只能接受纯对象,如果我们想要异步的获取数据,然后更新state状态的话,就需要一些 中间件来实现。

redux.js:200 Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.

我们可以通过redux-thunk来对传入的action是函数进行处理

  1. 安装redux-thunk的依赖
npm i redux-thunk
  1. 通过redux提供的拓展中间件的方法进行拓展
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))
  1. 通过中间件后,我们可以使用一些函数的action, 函数的回调参数有3个,第一个是加强后的dispatch, 第二个为getState, 第三个为自定义参数
dispatch((dispatch) => {
  setTimeout(() => {
    dispatch({type: 'ADD'})
  }, 3000)
}, 1000)

实现中间件

实现中间件之前,我们需要实现一些redux的辅助函数applyMiddlewarecreateStore

实现applyMiddleware

首先要确定applyMiddleware接受的参数

  1. createStore接受了applyMiddleware的返回值,增强了dispatch的功能,但是其他原来的功能需要保留
+  if (enhancer) {
+    return enhancer(createStore)(reducer)
+  }
  1. 从上面的createStore中可以看出applyMiddleware需要根据createStorereducer返回原来的功能,然后再原来的功能上增加dispatch的部分
// applyMiddleware
export function applyMiddleware(...middlewares) {
  return (createStore) => (reducer) => {
      // 保持原来的功能
      let store = createStore(reducer)
      return {
        ...store
      }
  }
}
  1. 在保持基本功能的情况下,我们需要通过中间件对于dispatch进行加强,来覆盖原来的dispatch的功能
  2. thunk一个函数,接受2个参数getState, dispatch,所以我们要创建这2个参数, 然后执行中间件的函数,通过闭包的方式保留getStatedispatch
export function applyMiddleware(...middlewares) {
  return (createStore) => (reducer) => {
      // 保持原来的功能
      let store = createStore(reducer)
+      let dispatch = store.dispatch
+      const midApi = {
+        getState: store.getState,
+        dispatch: (action, ...args) => {
+            return dispatch(action, ...args)
+        }       
+      }   
+     const middlewareChain = middlewares.map(middleware => middleware(midApi))
      return {
        ...store
      }
  }
}
  1. 现在我们要思考的是需要一个函数执行所有的中间件middlewareChain(并不知道applyMiddleware会接受几个中间件),然后返回一个加强版的dispatch来覆盖原来的store.dispatch, 我们假设有一个compose函数接收所有的中间件和原来的dispatch,然后返回一个加强后的dispatch

    1. 假设又一个compose函数
    export function applyMiddleware(...middlewares) {
      return (createStore) => (reducer) => {
          // 保持原来的功能
          let store = createStore(reducer)
          let dispatch = store.dispatch
          const midApi = {
            getState: store.getState,
            dispatch: (action, ...args) => {
              return dispatch(action, ...args)
            }
          }
          const middlewareChain = middlewares.map(middleware => middleware(midApi))
    +     dispatch = compose(middlewareChain)(dispatch)
          return {
            ...store
          }
        }
      }
    
    1. 实现compose函数, 实现middlewareChain的数组聚合,只有一个方法reduce来实现数组的聚合,然后需要返回一个函数,接受原来的dispatch
    // 先假设只有一个中间件或者没有中间件
    function compose(middlewareChain) {
    		  // 如果没有的话直接返回原来的dispatch
      if (middlewareChain.length === 0) {
          return arg => arg
       }
      if (middlewareChain.length === 1) {
         //只有一个函数的时候,返回函数,执行的时候会接受原来的dispatch
         return middlewareChain[0]
      }
      // 多个中间件 reduce 返回一个函数,执行一次就执行所有的函数
      return middlewareChain.reduce((a, b) => (...args) => a(b(...args)))
    }
    
    

总结

这里我们就实现了applyMiddlewarecreateStore,整体的代码

export function applyMiddleware(...middlewares) {
  return (createStore) => (reducer) => {
    // 保持原来的功能
    let store = createStore(reducer)
    let dispatch = store.dispatch
    const midApi = {
      getState: store.getState,
      dispatch: (action, ...args) => {
        return dispatch(action, ...args)
      }
    }
    const middlewareChain = middlewares.map(middleware => middleware(midApi))
    console.log(middlewareChain[0])
    dispatch = compose(middlewareChain)(dispatch)
    console.log(dispatch)
    return {
      ...store,
      dispatch
    }
  }
}

// enhancer 是applyMiddleware函数执行的返回值, reducer肯定要传入applyMiddleware中,
export function createStore(reducer, enhancer) {
  let state
  let listeners = []
  if (enhancer) {
    return enhancer(createStore)(reducer)
  }

  function getState() {
    return state
  }

  function dispatch(action) {
    state = reducer(state, action)
    listeners.forEach(listener => {
      listener && listener()
    })
    return action
  }

  function subscribe(listener) {
    listeners.push(listener)
    return () => {
      let index = listeners.findIndex(item => item === listener)
      listeners.splice(index, 1)
    }
  }

  // 触发初始化
  dispatch('init')
  return {
    getState,
    dispatch,
    subscribe,
  }
}


function compose(middlewareChain) {
  // 如果没有的话直接返回原来的dispatch
  if (middlewareChain.length === 0) {
    return arg => arg
  }
  if (middlewareChain.length === 1) {
    //只有一个函数的时候,返回函数,执行的时候会接受原来的dispatch
    return middlewareChain[0]
  }
  // reduce 返回一个函数,执行一次就执行所有的函数
  return middlewareChain.reduce((a, b) => (...args) => a(b(...args)))
}

实现中间件

首先我们要明白每一个中间件,需要保存上一个下一个中间件的执行过程(next),又需要保存接受action

  1. redux-thunkredux-logger的实现

    • 基于上面的实现,我们会首先执行中间件,然后传递2个参数给它dispatchgetState,
    • 然后需要保存执行下一个中间件的过程next 和 返回一个可以接受action的dispatch函数
    export function thunk({getState, dispatch}) {
      return (next) => {
        return (action) => {
          if(typeof action === 'function') {
            console.log('-thunk 执行-')
            return action(dispatch, getState)
          }
          return next(action)
        }
      }
    }
    
    export function logger({getState, dispatch}) {
      return (next) => {
        return action => {
          console.log('-pre state-', getState())
          // 触发动作再获取state
          console.log('next', next)
          const returnValue = next(action)
          console.log(returnValue)
          let nextState = getState()
          console.log('-next state-', nextState)
          return returnValue
        }
      }
    }
    
  2. 2个中间件的整体执行流程

    const store = createStore(reducer, applyMiddleware(thunk, logger))
    // App.js
    dispatch((dispatch, getState) => {
      setTimeout(() => {
        dispatch({ type: 'ADD' })
      }, 1000)
    })
    
    1. 运用过中间键后,每一次的dispatch都会经过thunklogger进行处理
    2. App.js中dispatch执行,会进入到加强过的thunk的回调函数中接受action执行
    (action) => {
      if(typeof action === 'function') {
        console.log('-thunk 执行-')
        return action(dispatch, getState)
      }
      return next(action)
    }
    
    1. 知道是函数的话,然后会执行这个函数,传入dispatchgetState参数
       setTimeout(() => {
        dispatch({ type: 'ADD' })
      }, 1000)
    
    1. 这里又会执行到dispatch函数,所以又会进入到第二部的加强版的dispatch中,这次action不是一个函数,就执行到下一个中间件return next(action)
    2. 由于闭包的关系,next中间件就是logger函数的部分, 然后执行完成
    action => {
      console.log('-pre state-', getState())
      // 触发动作再获取state
      console.log('next', next)
      const returnValue = next(action)
      console.log(returnValue)
      let nextState = getState()
      console.log('-next state-', nextState)
      return returnValue
    }