redux源码阅读笔记(二)

建议:有 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源码阅读笔记(二))