美文网首页
Redux 分析

Redux 分析

作者: 嘴角45度 | 来源:发表于2019-06-24 14:00 被阅读0次

    Redux 是什么

    Redux 是 Javascript 状态容器,提供可预测化的状态管理

    Redux的功能及作用

    • 将状态统一放在一个 state 中,由 store 来管理这个 state
    • store 按照 reducer 的 shape 创建
    • store 通过 dispatch 一个 action,再由 reducer 统一更新
    • 每次使用dispatch 后,可以通过 subscribe 来监听store的变化

    适用场景

    • 多页面需要共享的数据
    • 一个地方的数据发生改变,其它地方的数据也要同步改变

    不适用场景

    • 数据只在单组件或者单作用域中使用
    • 数据更改后不需要重新渲染出来

    Redux 与全局对象有什么区别

    • Redux 中的数据存放在store中,只能通过 dispatch 一个 action 去修改,方便调试,任何改变都会有一次更改记录
    • 全局对象的值可以直接修改,修改后无任何记录,调试不方便,使用不规范还容易造成全局变量污染

    实现原理解析

    Redux 的工作流程是:

    • 创建 store,即:createStore()
    • 将多个 reducer 合并为一个 reducer,即 combindReducers()
    • 应用中间件,即 applyMiddleware()

    在源码目录中,还包含了 compose, bindActionCreators 两个文件,这两个文件主要是用于提供一些方法的

    下面分别对它们的源码进行解析

    createStore

    关键代码如下:

    export default function createStore(reducer, preloadedState, enhancer) {
      if (enhancer 有效) {
        return enhancer(createStore)(reducer, preloadedState)
      }
    
      let currentReducer = reducer
      let currentState = preloadedState
      let currentListeners = []
      let nextListeners = currentListeners
      let isDispatching = false
    
      // 获取当前 state
      function getState() {
        return currentState
      }
    
      // 添加一个监听函数,每当 dispatch 被调用的时候都会执行这个监听函数
      function subscribe(listener) {
        let isSubscribed = true
    
        // 对currentListeners进行一个浅拷贝
        ensureCanMutateNextListeners()
        // 将新的监听对象push到监听数组中
        nextListeners.push(listener)
    
        return function unsubscribe() {
          isSubscribed = false
          ensureCanMutateNextListeners()
          const index = nextListeners.indexOf(listener)
          nextListeners.splice(index, 1)
        }
      }
    
      function dispatch(action) {
        try {
          isDispatching = true
          // 根据当前的 state, 以及传入的 action,更新当前的 state
          currentState = currentReducer(currentState, action)
        } finally {
          isDispatching = false
        }
    
        const listeners = (currentListeners = nextListeners)
        for (let i = 0; i < listeners.length; i++) {
          const listener = listeners[i]
          // 执行每一个的监听方法
          listener()
        }
    
        return action
      }
    
      return {
        dispatch,
        subscribe,
        getState,
      }
    }
    
    

    从上面可以看出,通过 createStore 方法创建了一个store,创建好后并没有直接将 store 返回,而是返回了几个与操作 store 相关的方法,主要有 getState、subscribe、dispatch

    getState
      function getState() {
        return currentState;
      }
    

    由 createStore 创建的 store,要直接访问它里面的数据只能通过 getState 的方式,通过 getState 返回的数据是 currentState 对象的一个引用,可以直接修改它,但是 reudx 建议最好不要这样做。

    compose

    compose 的作用是将多个方法进行一个包装

    export default function compose(...funcs) {
      if (funcs.length === 0) {
        return arg => arg
      }
    
      if (funcs.length === 1) {
        return funcs[0]
      }
    
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    }
    

    compose(funcA, funcB, funcC, funD)

    输出结果为:

    funcA(funcB(func(funcD())))

    combineReducers

    了解 combineReducers 得先了解 reducer 它是什么?它的作用是什么?

    reducer 是 redux 通过 dispatch 一个 action 之后,用来更新 store 的一个纯函数方法。
    更新 store 的 reducer 可以只有一个,但是由于业务量可能会很大,功能模块划分很多,如果只使用一个 reducer 来更新数据的话 reducer 会很长,后期遇见 bug 时排查会很困难,所以可以将 reducer 按照 store 创建时的结构拆分成多个 reducer,最后通过 combineReducers 合并成一个 reducer。

    export default function combineReducers(reducers) {
      // 拿到需要合并的 reducer 的所有的 key
      const reducerKeys = Object.keys(reducers)
      const finalReducers = {}
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
    
        // 对 reducer 进入一个验证,筛选出可用的 reducer
        if (typeof reducers[key] === 'function') {
          finalReducers[key] = reducers[key]
        }
      }
      // 拿到所有可用的 reducer 的 key
      const finalReducerKeys = Object.keys(finalReducers)
    
      let shapeAssertionError
      try {
        // 对最后的 reducer 进行一个验证,分别使用 reducer 更新一个固定初始值和一个随机值,看它的返回结果是否正确
        assertReducerShape(finalReducers)
      } catch (e) {
        shapeAssertionError = e
      }
    
      return function combination(state = {}, action) {
        if (shapeAssertionError) {
          throw shapeAssertionError
        }
    
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
          // 拿到可用的 reducer key
          const key = finalReducerKeys[i]
          // 拿到可用的 reducer
          const reducer = finalReducers[key]
          // 根据 key 拿到之前的 state 值
          const previousStateForKey = state[key]
          // 根据之前的 state 值,结合 action,通过 reducer 方法生成单个新的 state
          const nextStateForKey = reducer(previousStateForKey, action)
          // 将最新的值存放在最新的 state 中
          nextState[key] = nextStateForKey
          // 将新值和旧值进行一个比较,判断是否有发生改变
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        // 如果有发生改变,就返回新的 state, 如果没有改变,就返回之前的 state
        return hasChanged ? nextState : state
      }
    }
    

    applyMiddleware

    什么是中间键?先看看它的核心代码

    export default function applyMiddleware(...middlewares) {
      // 调用applyMiddleware方法,会返回一个创建 store 的函数
      return createStore => (...args) => {
        const store = createStore(...args)
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        // 对中间键进行一个循环,给每一个中间键都传入当前的 store 以及 dispatch 方法,然后执行它
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 通过 compose 将多个中间键用柯里化的方式使其调用
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    }
    

    经过上面步奏,中间键一般会返回一个 action 函数,将它对dispatch进行改写,下次再调用 dispatch,实则是调用改写后的 dispatch,这样也就在dispatch 与 reducer 中间多加了一层可自定义执行的方法,在 dispatch 与 reducer 之间,我们可以执行异步操作,如请求数据,打印日志等功能。等这些功能都执行完成后,再dispatch 一个真正的纯 action, 用于修改 reducer, 这样在不修改原有代码的基础上,达到了中间插入操作的功能。

    如何与 React 框架结合使用

    使用 react-redux 插架帮助我们实现

    了解具体实现,点击 react-redux 源码解析

    Q & A

    1. dispatch 是同步的还是异步的?

      dispatch 是同步更新 store的。

    2. 为什么在使用 react 渲染的时候它是异步的呢?

      因为在 redux 结合 react 的使用时,实则是使用 connect 这一个高阶组件来实现数据的更新的,在监听到 store 的数据发生改变后,调用 react 的 setState 方法来重新渲染页面,而 setState 是异步的,所以并不是 dispatch 是异步的,而是 setState 是异步的。

    3. redux 的 subscribe 是怎么实现的?

    参考链接

    相关文章

      网友评论

          本文标题:Redux 分析

          本文链接:https://www.haomeiwen.com/subject/lonnqctx.html