Ahonn's BlogAhonn's Blog

Redux 源码解析

#JavaScript#React#Redux

三大原则#

单向数据流#

源码解析#

Redux 提供的 API 主要有:

createStore#

createStore 用来创建 Redux store,用于存放所有的 state。

通过 const store = createStore(reducer, initialState) 创建 store,只能通过 dispatch action 的方式来改变 state。同时提供了 getStatesubscribe 等方法。

export default function createStore(reducer, preloadedState, enhancer) {
  // ...

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

  // ...

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
js

当使用 createStore 方法时,dispatch 了名为 @@redux/INIT 的 action。表示已经初始化了 store。

dispatch 方法的实现:

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

currentState = currentReducer(currentState, action) 执行 action 对应的 reducer 操作产生新的 state,如果有监听函数的话,按顺序触发监听函数。

监听函数由 subscribe 方法添加,subscribe 方法的返回一个 unsubscribe 函数,用来取消监听。

subscribe 方法的具体实现:

function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

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

实际上 subscribe 仅是在 nextListeners 中添加想要执行的监听函数,并返回了用于删除该函数的 unsubscribe 方法

combineReducers#

combineReducers 用于合并不同的 reducer 函数,返回唯一的一个 reducer 函数供 createStore 使用

export default 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)

  // ...

  return 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]
      const nextStateForKey = reducer(previousStateForKey, action)
	  // ...
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}
js

首先处理传入的 reducer 对象(包含不同的 reducer 函数),得到键值为 function 类型的对象 finalReducers 与一个包含其键的数组 finalReducerKeys

combineReducers 返回一个 reducers 函数用于处理 action,循环所有的 reducers 传入 action 进行处理,当 state 不变时,返回原来的 state

applyMiddleware#

applyMiddleware 的作用是添加中间件并执行中间件,主要是通过 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)))
}
js

重点是 funcs.reduce((a, b) => (...args) => a(b(...args))) ,通过函数式编程中常见的 f(g(args)) 形式串起来,返回一个函数,函数由内到外执行传入之前传入的函数数组,函数的参数将会传递到第一个执行的函数中。

简单的例子:

var f1 = function(a) {
	console.log('f1: '+ a)
	return a * 10
}

var f2 = function(a) {
	console.log('f2: ' + a)
	return a + 1
}
const funcs = [f1, f2]

const func = funcs.reduce((a, b) => (...args) => a(b(...args)))
func(1)
js

compose

在 applyMiddleware 中 dispatch = compose(...chain)(store.dispatch),即传入 compose 返回的函数的参数为 store 原来的 dispatch 函数。

也就是说,添加了中间件后,执行 dispatch 函数将会首先依次执行传入的中间件, 最后再执行原先的 store.dispatch

关于 Redux midddleware 有篇文章写得很不错:compose and middleware 源码分析

bindActionCreators#

bindActionCreators 方法说白了就是把 action creators 转成拥有同名 keys 的对象,并使用 dispatch 将对象包裹起来,从而能够直接调用函数 dispatch action

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

export default 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
}
js

这部分得源码很简单,只是简单的判断 actionCreators,是对象的话就返回一个键值为绑定了 dispatch 的函数的对象

思考总结#

很久之前就学会了 Redux 的『使用』,但是却不明白到底是什么实现的,只知道应该那样使用。

阅读了源码之后对 Redux 整个流程更加清晰,有利于之后的使用。也更加理解 Redux 单向数据流的这种思想,虽然使用上更加繁琐,但是如果数据复杂起来,这种单向数据流的管理方式还是利大于弊的。

参考链接