redux源码结合实践深刻解析

背景

redux做为前端状体管理中最亮眼的那个仔,很是有必要弄清楚他的原理。本文将从源码结合实践一块儿来从新认识redux。纯干货分享!!!javascript

redux相对来说是相对比较复杂的状态管理工具。实现一个全局状态管理工具,经过一个全局变量和一些方法,便可实现的东西,那么为何redux须要提出action,store,dispatch,reducer等一系列概念?提出这些概念的做用或者说动机是什么?但愿读者能从这篇文章中深刻理解这些概念存在的价值和意义。前端

export const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
);
复制代码

咱们常常看到这段代码,本文将从以createStore做为入口顺藤摸瓜带你认识整个框架。下面源码是v3.7.2版本的代码。java

createStore

首先来看createStore函数源码, 为了方便理解和阅读省略了不少无关的代码,你们在阅读的时候能够折叠起来看。react

export default function createStore(reducer, preloadedState, enhancer) {
   // 若是只有两个参数,而且第二个参数是函数的话,将会传递给enhancer
   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
    
    // 省略一堆判断逻辑
    return enhancer(createStore)(reducer, preloadedState)
  }
   
   // 一堆方法定义
   dispatch({ type: ActionTypes.INIT });
   return {
    dispatch,  // 重点讲述
    subscribe, // 重点讲述
    getState, // 返回state的方法
    replaceReducer, // 高级用法,目的在于分包时,动态更换reducer
    [$$observable]: observable
  }
}
复制代码
  • 从代码中能够看到store的返回值是一个对象,具备多种方法
  • enhancer的做用是功能扩展,返回值是一个store, enhancer函数的写法举例
function myEnhancer(createStore){
    return (reducer, preloadedState, enhancer) => {
       //建立store以前, do someSting
        const store = createStore(reducer, preloadedState, enhancer)
        //store以后, do something
        return store;
    }
}
复制代码
  • store建立以后,就会dispatch一个默认的初始action,来作初始化。这步操做能够类比与函数自执行,目的是为了让每一个reducer返回他们默认的state构成初始全局state。
  • 全局state其实就是一个普通对象函数,其余操做都是来辅助管理该state

dispatch

dispatch是咱们的重头戏,后面仍是介绍他,咱们先看下,当咱们dispatch({ type: 'INCREACE', payload: 1})会发生些什么呢。redux

function dispatch(action) {
    // 各类检查acton类型
    try {
      isDispatching = true
      // currentState是原来的state
      // currentReducer就是一开始createStore时传入的reducer
      currentState = currentReducer(currentState, action)
      // reducer以后返回的是更新后的新的state
    } finally {
      isDispatching = false
    }

    // 更新监听者函数
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
复制代码
  • dispatch触发一个action,执行reducer,而后更新监听者,最后返回action自己。这里为何要返回action呢?答案是为了中间件的链式复合,在中间件部分会详细解释。
  • action的类型检查中要求,action必须是一个普通对象,必须有type属性
  • reducer是一个函数,接收两个参数state和action,并返回新的state,初始化时,state多是undefined,所以经过触发默认action,来返回reducer的初始state。reducer常见格式以下:
function todos(state = [], action) {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case 'COMPLETE_TODO':
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: true
          })
        }
        return todo
      })
    default:
      return state
  }
}
复制代码
  • reducer执行完成后,更新state,而后触listeners函数,没有任何参数,其中一个的应用是react-redux中的高阶组件connect更新机制,后面会深刻解析react-redux,请持续关注哦!

subscribe

subscribe是一个简单的监听者模式,该函数主要是收集监听者。源码很简单以下bash

function subscribe(listener) {
    // 检查listener类型
    let isSubscribed = true
    
    ensureCanMutateNextListeners() 
    // 该函数会复制一份currentListeners
    // 保障更新期间其余listener不受影响
    nextListeners.push(listener)
    
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }
      // 省略部分错误检查
      isSubscribed = false
    
      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
      // 下次运行时 currentListeners会从新从nextListeners中取,能够看dispatch的代码
      // 做者这样作的目的主要是为了防止dispatch执行期间发生subscribe或者unsubscribe引起异常错误
    }
}
复制代码

到这里,整个redux的核心功能就介绍的差很少了,可是redux的威力并无体现出来,接下来咱们将介绍redux的扩展功能中间件。闭包

applyMiddleware

该函数是一个enhancer函数,由redux实现提供, 用来嵌入中间件,也是咱们常用的一个工具函数。架构

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    // 特别注意这个dispatch是使用let赋值的
    // 这个预约义是为了防止用户提早使用,此时没法触发其余中间件
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
      // 这个dispatch方法不能在next函数前使用
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
复制代码
  • 输入参数是一些列中间件,返回值是一个store对象(能够对照createStore的代码),dispatch函数进行了封装。
  • compose函数实现了一个简单的洋葱模型,上一个函数的输入做为下一个函数的输出,后面会详细介绍。
  • 从代码中发现,每一个中间件会输入getState和dispatch对象,返回值须要知足compose函数要求。举例以下,下面例子中能够记录每一个action到更新state所花费的时间。
function loggerMiddleware({getState, dispatch}){ // 这部分对应的是middleware(middlewareAPI)
    // 这块区域不能使用dispatch函数,不然会抛出错误!!
    return next => action => {
        console.time(action.type);
        const result = next(action);
        // result 对象是一个action类型的对象,若是中间件未修改过该值,则全等,通常来说,action不该该被修改
        console.timeEnd(action.type);
        return result;  // 将会传入下一个中间中
    }
}
复制代码

在书写中间件的时候,咱们发现内部闭包了多个函数,若是部分函数采用async等方式的话,就能够实现异步操做,解决反作用的问题,redux-thunk正是借用这种方式实现,感兴趣的同窗能够学习下,代码只有14行,这里就不展开讨论了。app

  • next函数是上一个中间件的返回值,是上一个中间件封装后返回的dispatch,next(action)的做用至关于dispatch(action),他会触发后续的中间件,所以next命名比较形象

compose

compose是一个函数构造器,返回一个新的函数。相似数学中的函数f(x),g(x),h(x)复合为f(g(h(x)))。上一个函数的输出做为下一个函数的输入。框架

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)))
}
复制代码
  • 出于js单个返回值的限制,每一个函数的参数只能有一个
  • 若是参数x是一个值的时候,compose函数执行后也会获得一个值, 即 compose(a,b,c)(x)也会返回一个值。举例(九折后满500再减50):
function couponA(next) {
    if(next >= 500){
        return next - 50;
    }
    return x;
}
function couponB(next){
    return next * 0.9;
}
const discount = compose(couponA, couponB);
discount(1000); // 850
复制代码

当参数是一个值的时候,没法实现回旋镖的形式。上述例子实际上是一个简单的职责链模式,感兴趣的能够深刻挖掘,在电商打折规则中特别实用

  • 若是参数是一个函数的时候,每一个中间件也返回一个函数,如applyMiddleware中的dispatch。因为dispatch是一个函数,能够利用函数调用时执行的特色,实现回旋镖型的中间件,如上述loggerMiddleware,能够记录dispatch所花费的时间。
  • compose函数中处理函数是从右向左执行,即最后一个函数先执行。

combineReducers

这是一个工具函数,能够将多个reducer聚合起来,返回值是一个reducer(这是一个函数)

// reducers是一个
export default function combineReducers(reducers) {
    // 省略对reducers作了一堆检查
    // 下面这句是为了好理解,我杜撰的,非真实源码
    const finalReducers = {...reducers}
    const finalReducerKeys = Object.keys(finalReducers);
    function combination(state = {}, action) {
        // 此处省略了一些检查
        let hasChanged = false
        const nextState = {}
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i]
          const reducer = finalReducers[key]
          const previousStateForKey = state[key]
          // 这里以key划分命名空间,previousStateForKey为指定key下的state
          const nextStateForKey = reducer(previousStateForKey, action)
          if (typeof nextStateForKey === 'undefined') {
            // 每一个reducer都应该有返回值 
            const errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
    } 
}
复制代码
  • 使用combineReducers后,对应的state也具备与reducers对象具备相同的结构。

bindActionCreators

该函数是redux提供的一个工具函数,首先要弄清楚action和actionCreator的关系。action是一个普通对象,actionCreator是一个构造action对象的函数

bindActionCreator的目的是将actionCreator与dispatch结合构造出一个可以直接触发一系列变化的Action方法 bindActionCreators就是将多个actionCreator转化为Action方法

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  // 省略一系列检查
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
复制代码

在实践中,结合reat-redux的connect的第二个参数mapDispatchToProps为例,展现actionCreators转化为能够直接运行的方法。

const actionCreators = {
    increase: (payload) => ({ type: 'INCREASE', payload }),
    decrease: (payload) => ({ type: 'DECREASE', payload })
}

@connect(
    state => state,
    dispatch => ({
        actions: boundActionCreators(actionCreators, dispatch)
    })
)
class Counter {
    render(){
        <div>
            <button onClick={() => this.props.actions.increase(1)}>increase</button>
            <button onClick={() => this.props.actions.decrease(1)}>decrease</button>
        </div>
    }
}
复制代码

总结

  1. redux管理状态是经过一个currentState对象来存储全局状态,可是将修改状态拆分为了dipatch(action)和reducer两部分,大大提升工具库的灵活性和想象空间。
  2. 理解并学会redux中间件的写法,更加深刻了解compose函数
  3. redux相对比较复杂,但在其基础上衍生了大量的第三方工具库,足见其生命力,在实践中体会做者架构的深意。
  4. 为了便于理解,删除了不少类型判断,这些类型判断可以帮助开发者更好的调试代码,一样很是重要,你们在本身研究源码时,不要忽视这些细节。
  5. 文章中包含了本身大量的理解,描述和理解有不妥之处,请批评指正!!!
相关文章
相关标签/搜索