duhongjun / blog

my blog ~
https://duhongjun.github.io/blogsite
0 stars 0 forks source link

Redux源码解读 #6

Open duhongjun opened 6 years ago

duhongjun commented 6 years ago

Redux 源码解读

Redux 本身提供的功能有什么呢?

然后我们看一下源码的目录结构:

image

看起来确实也没有多少, 接下来我们一个一个看

1. createStore

大致的结构如下(省略了一些很好理解的参数判断/抛错逻辑)

function createStore(reducer, preloadedState, enhancer) {

  if (typeof enhancer !== 'undefined') {
    // 会和applyMiddleware一起说
    return enhancer(createStore)(reducer, preloadedState)
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  // 获取当前state
  function getState() {
    // ...
  }
  // 增加监听函数, 每当调用dispatch时会触发
  function subscribe(listener) {
    // ...
  }
  // 触发一个action, 执行对应的reducer更新state,并且执行所有通过`subscribe`注册的监听函数
  function dispatch(action) {
    // ...
  }

  // 替换当前的reducer
  function replaceReducer(nextReducer) {
    // ...
  }

  // 对于这个函数,是不直接暴露给开发者的,它提供了给其他观察者模式/响应式库的交互操作,
  // 具体可看 https://github.com/tc39/proposal-observable
  function observable() {
    // ...
  }

  // 当一个store被创建, 一个"INIT" action会被触发,
  // 所以每个reducer 返回他们的初始值从而填充state树
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
  1. getState
function getState() {
  // 没啥好说的..
  return currentState
}
  1. dispatch

执行 reducer, 更新 state, 然后遍历执行所有通过subscribe注册过的监听函数

function dispatch(action) {
  try {
    isDispatching = true
    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
}
  1. subscribe

内部有一个isSubscribed变量标识是否已经订阅,将监听函数放入nextListeners, 返回一个unsubscribe函数用于解绑, 需要注意的是无论绑定还是解绑都是在下次dispatch触发时才会生效

// 先判断是否是同一个引用, 如果是会先进行拷贝
 function ensureCanMutateNextListeners() {
      if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
     }
   }

function subscribe(listener) {
  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }
    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}
  1. replaceReducer

替换reducer, 并触发一个REPLACE action, 更新 store 的初始值

function replaceReducer(nextReducer) {
  currentReducer = nextReducer
  dispatch({ type: ActionTypes.REPLACE })
}

2. combineReducers

我们通常是将 reducer 分模块编写, 最后使用这个 API 将多个 reducer 组合成一个, 然后传入createStore

主要结构如下:

// 当触发一个action并执行reducer后有任何key对应的state值为undefined的时, 抛出一个Error
function getUndefinedStateErrorMessage(key, action) {
  // ...
}
// 遍历传入的state的key, 如果组合过的reducer中不包含对应的key, 抛出warning
function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
  // ...
}
/**
 * 1. 遍历执行所有的reducer, 传入`INIT`action 和 值为undefined 的state 作为初始值 ,
如果返回的state值是undefined, 说明没有"设置初始state", 抛出异常
 * 2. 如果第一步没问题, 会接着再调用一次reducer, 传入一个随机的action type 和
值为undefined的state作为初始值, 如果返回值为undefined, 说明没有遵守
`遇到未知的 action 时,一定要返回旧的 state`的约定, 抛出异常
 **/
function assertReducerShape(reducers){
  // ...
}

/**
 * 合并传入的reducer对象, 返回一个新的 reducer 函数给 `createStore` 使用。
 */
export default function combineReducers(reducers) {
  // ...
}

下面说说combineReducers这个方法

  1. 首先是遍历传入的 reducers 对象, 然后以同样的 key 赋值给finalReducers, 值就是对应的 reducer 函数.
  2. 调用assertReducerShape方法判断每个 reducer 是否符合基本规约
  3. 返回一个标准的 reducer 函数,接受一个 state 和 action 参数
  4. 每次调用 dispatch 时, 都会去遍历finalReducers, 获得当前 key 对应的nextState, 如果当前 state 和 nextState 不同, 将 hasChanged 标记置为 true,返回完整的 nextState,否则返回当前 state 即可.
function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  // 遍历赋值
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 判断reducer是否符合规范
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }
    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    // 遍历获取对应key的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
  }
}

3. compose

从右到左来组合多个函数,并返回一个最终的函数。compose(f, g, h) 等于 (...args) => f(g(h(...args))), 不懂的话可以看一下Array.reduceapi

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

4. applyMiddleware

applyMiddleware 接收多个参数,并返回一个以 createStore 为参数的 function,此 function 经过一系列的处理之后返回了 createStore 里面的所有方法和重写的 dispatch。

function applyMiddleware(...middlewares) {
  // 可以去看createStore对enhancer的调用方式:  return enhancer(createStore)(reducer, preloadedState)
  return createStore => (...args) => {
    // 首先创建store
    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.`
      )
    }
    // 调用每个中间件时传递的参数, 即对应着第一层函数的store
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 中间件的格式:  (store) => next => (action) => {}
    // 假设现在传入的为3个中间件数组: [m1, m2, m3]
    // 则 chain现在为[next => (action) => {  next(action); //m1 },  next => (action) => {  next(action); //m2 }, next => (action) => { next(action); //m3 }]
    // 为了直观, 把箭头函数改写一下: [function m1(next) { return (action)  { //m1 }}, function m2(next) { return (action) => { //m2 }}, function m3(next) { return (action) => { //m3 }}]=>
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 让我们回忆一下compose返回的结果: compose(f, g, h) 等于 (...args) => f(g(h(...args)))
    // 所以 compose(...chain) 的结果为 (...args) => m1(m2(m3(...args)))
    // 即 dispatch = compose(...chain)(store.dispatch) = m1(m2(m3(store.dispatch)))
    // 然后把最后组合结果赋值给dispatch并返回, 这样在我们调用的时候, 其实就是使用了当前这个增强后的dispatch
    // 我们调用dispatch({type: xxx})时, 就相当于 (action) => {  next(action); //m1 }({type: xxx})
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

那么中间件的执行顺序是怎样的呢?

假设中间件最内部函数体为

{
  console.log('in')
  next(action)
  console.log('out')
}

中间件的执行顺序自然就是:

m1 in => m2 in => m3 in => dispatch => m3 out => m2 out => m1 out ;

执行顺序与 koa 的洋葱模型一致(见下图左)

如果我们在 dispatch 中又调用了 dispatch 会怎么样呢? 其实和在其他地方是一样的, 会从头再来一遍(如下图右),

image

我们顺便看一下redux-thunk的源码, 其实就有这方面的应用

function createThunkMiddleware(extraArgument) {
  return store => next => action => {
    if (typeof action === 'function') {
      return action(store.dispatch, store.getState, extraArgument);
    }

    return next(action);
  };
}

redux-thunk 做的事情就是判断 action 类型是否是函数,若是,则执行 action,若不是,则继续传递 action 到下个 middleware。

4. bindActionCreators

bindActionCreators 函数可以生成直接触发 action 的函数; 源码很简单, 就不详细说了

惟一会使用到 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 dispatch 或 Redux store 传给它。 源码如下:

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

function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(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
}