美文网首页我爱编程
redux源码阅读笔记(二)

redux源码阅读笔记(二)

作者: anshi | 来源:发表于2018-04-11 16:17 被阅读0次

    建议:有 redux 的实践后再来看相关的文章。你需要先知道 redux 能让你做什么,才会激起对源码的欲望。

    • 推荐看看这篇文章 Redux 卍解,回顾一下 redux都给你提供了哪些 api ,能干些什么。
    • 不准备把行行代码都贴出来,建议自行打开源码同步阅读。

    redux 的源码内容并不多,可以说很少,相比 koa.js 会多一点 (笑)。源码结构如下图:


    源码结构

    combineReducers.js

    combineReducers的大致结构如下

    image.png
    回忆我们使用 combineReducers 的时候
    //a.reducer
    const aR =  function(state = initialState, action) {
      switch (action.type) {
      case ...
      default:
        return state;
      }
    }
    //b.reducer
    const bR =  function(state = initialState, action) {
      switch (action.type) {
      case ...
      default:
         return state;
      }
    }
    
    export default combineReducers({aR,bR});
    

    对应到源码,就不难理解使用时的一些“要求”。(比如 接受的参数对象 的 key 要与对应的 state 对应。)


    image.png

    代码先对 参数 reducers 进行一次遍历查空,值非空且为 function 的键值对组成最终的 recuder。题外话:现在遍历对象还是得靠 Object.keys 吗?

    //assertReducerShape
      const reducerKeys = Object.keys(reducers)
      const finalReducers = {}
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
    
        if (process.env.NODE_ENV !== 'production') {
          if (typeof reducers[key] === 'undefined') {
            warning(`No reducer provided for key "${key}"`)
          }
        }
    
        if (typeof reducers[key] === 'function') {
          finalReducers[key] = reducers[key]
        }
      }
    

    按部就班下来,finalReducers 就是我们刚才得到的新对象,去往 assertReducerShape 内部,可以发现该函数内并没有做影响外部的事情,也没有返回值,该函数的用意应该是试探性的运行当前的 reducer,确定 reducer 被调用后没有返回 undefined ..而且还进行了两次判断...@todo 暂时还未明白为何要两次判断

    let shapeAssertionError
      try {
        assertReducerShape(finalReducers)
      } catch (e) {
        shapeAssertionError = e
      }
    

    combineReducers 的返回值仍然是函数,毕竟我们只是将多个 reducer 函数合并成一个函数;核心逻辑代码如下,可以看出 进入 combineReducers 的 reducers 的 key 是需要和 state 下对应 key 一致的;赋值给 nextState 前还需要判断非 undefined ,同时只要任一的 reducer 改变了 state,hasChanged 的值都为true,都将返回新的 state。

       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]
         const nextStateForKey = reducer(previousStateForKey, action)
         if (typeof nextStateForKey === 'undefined') {
           const errorMessage = getUndefinedStateErrorMessage(key, action)
           throw new Error(errorMessage)
         }
         nextState[key] = nextStateForKey
         hasChanged = hasChanged || nextStateForKey !== previousStateForKey
       }
       return hasChanged ? nextState : state
    

    其实核心逻辑的代码量可能还不如判空,判 undefined ,输出警告等行为的代码量,但是一个优秀的插件,对不合理的值应该有合适的处理而不是等着运行环境给我们报错,这是很多新手前端(比如我)会忽视的内容,日常可能还得靠各种报错一点点累计经验...以至于不会重视报错,认为有错改一下就好了。

    bindActionCreators.js

    这个函数我们经常用...但你可能并不太了解他的作用:

    const mapStateToProps = state => {
      return {
        stateKey: state.stateKey
      };
    };
    const mapDispatchToProps = dispatch =>
      bindActionCreators(
        {
          oneAction
        },
        dispatch
      );
    
    export default connect(mapStateToProps, mapDispatchToProps)(Component);
    

    以前的认知就是执行了这些,就可以在组件下直接 this.props.oneActionCreator(actionParam)来发起一个 action,而不是 this.store.dispatch(oneActionCreator(actionParam))

    我们先从源码的返回值 开始看,如下图。


    image.png

    函数返回 boundActionCreators 我们再追溯的看这个变量如何赋值的。

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

    可以大致得知,函数主要做了这样的操作。


    图左是actionCreators,图右是返回值

    那关键步骤就是bindActionCreator 了,而其也十分简单...

    function bindActionCreator(actionCreator, dispatch) {
      return function() {
        return dispatch(actionCreator.apply(this, arguments))
      }
    }
    

    朋友们还记得 dispatch 做了什么吗~ 分发action!没错,bindActionCreator直接帮你在创建 action 时 dispatch 这个 action。
    综述,bindActionCreators.js 的处理过程如下图。

    image.png
    可是这里为什么要使用 apply 呢?为什么不直接 return dispatch(actionCreator(arguments)) ?我们知道以函数形式调用的函数的 this 的值是全局对象 ( javascript 权威指南第八章),使用 apply 后间接等于方法调用,这里形如 this. actionCreator(arguments),方法形式调用的函数其上下文 (this) 为调用的对象。所以这里的 this 是打算将调用 bindActionCreator 内部函数的上下文传递给 actionCreator 使用。

    applyMiddleware.js

    image.png

    该模块返回函数 applyMiddleware ,参数 ...middlewares 类似于用 arguments 来接受一系列参数。函数内部返回值仍然是函数。注意 返回的值 是用于 createStore 的第三个参数 enhancer。

    return createStore => (...args) => {
        const store = createStore(...args)
        let dispatch = () => {
          throw new Error(
            `Dispatching while constructing your middleware is not allowed. ` +
              `Other middleware would not be applied to this dispatch.`
          )
        }
        let chain = []
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        dispatch = compose(...chain)(store.dispatch)
    
        return {
          ...store,
          dispatch
        }
      }
    

    箭头函数真的不太适合阅读...(可能是我不太习惯)

    return function (createStore) {
      return function (...args) {
        const store = createStore(...args)
        ...
      }
    }
    

    符合 enhancer 在 createStore 的逻辑enhancer(createStore)(reducer, preloadedState)
    观察函数的返回值,可以得知函数内部只改动了 dispatch 方法,再看源码。

    dispatch = compose(...chain)(store.dispatch)

    compose 方法是另一个文件导入的,源码也不多,比较关键的一句是 return funcs.reduce((a, b) => (...args) => a(b(...args))),但是这一句让不熟悉函数式编程的我思考了很久......

    compose(...chain),输出这一句的执行结果

    function (...args){
      return a( b(...args) )
    }
    

    看起来只是个普通的函数,只是这里的 a ,b 是谁呢?ಥ_ಥ
    我们知道 reduce 返回值是最后一次累加计算的结果,compose(...chain) 的返回值仍然是个函数,函数内的 a 就是上一次累加返回的结果,b 是当前项,也就是 chain 的最后一项;上一次累加结果仍然是 这个普通的函数,只不是此时的 b 是 chain 的倒数第二项,a 是再上一次的累加返回结果...

    dispatch = compose(...chain)(store.dispatch)
    此时再看这一句,其实就是:先以参数,运行了 chain 的最后一项函数,返回值做为参数被 a 调用,a是上一次的返回值,再次跳回 2 行,此时的 b 是 chain 的倒数第二项...以此类推,到chain 的第二项时(reduce 没有 设置初始值时,accumulator 的默认值为调用数组的第一项,同时从第二项开始累加计算),以 chain 第三项运行的返回值做为参数,调用 第二项,其后的返回值,做为参数 调用 chain 的第一项,然后 执行 3行,再次跳回3 行 ... 返回值 赋给 dispatch。

      // funcs 是该函数的参数数组。
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    
    //可以转换成
    0  return  funcs.reduce( function (a,b) {
    1     return function (...args){
    2       return a( b(...args) )
    3   }
    4  })
    

    总结下来就是...
    假设数组 chain 是 [ fun1, fun2, fun3],相当于最后以
    fun1(fun2(fun3(store.dispatch)))这样的形式调用了。

    写到这里时我并不是十分了解中间件的作用,不过从此处的代码可以推断,中间件需要接受一个形如 middlewareAPI 的参数,且返回值仍然是函数,返回值很有可能仍然是 store.dispatch...

        let chain = []
    
        const middlewareAPI = {
          getState: store.getState,
          dispatch: (...args) => dispatch(...args)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
    

    也就是说中间件主要对 store.dispatch 进行一些改造,同时也拿了 getState 方法做一些事情。


    image.png

    此时在看 applyMiddleware 的用法,至少不会觉得茫然吧...(大概 ಠ౪ಠ)

    const enhancer = applyMiddleware(
      thunk,
      createLogger()
    );
    
    const store = createStore(rootReducer, initialState, enhancer);
    

    初学 redux 我觉得最困难的就是记很多名词,很多规矩,redux 规定了很多东西,让我摸不着头脑。为此我送上一份源码脉络梳理图。
    最后送上一份源码脉络梳理图,画的有些粗略... 本意就是希望看到图还能回忆起各个函数大概做了些什么事情...

    redux代码脉络.png

    相关文章

      网友评论

        本文标题:redux源码阅读笔记(二)

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