最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Redux原理以及中间件的实现

    正文概述 掘金(sunnyhuang519626)   2021-01-21   721

    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-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
      }
      

    起源地下载网 » Redux原理以及中间件的实现

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元