SunShinewyf / issue-blog

技术积累和沉淀

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

redux源码解读

SunShinewyf opened this issue · comments

redux虽然强大,但是它的源码确实很简单


createStore

createStore返回的是一个对象,并且这个对象有如下成员函数dispatch,subscribe,getState,replaceReducerobservable

下面看它的核心源码(已经去掉一些对参数的判断):

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer, preloadedState)
  }

这一段是通过传入的参数类型来做判断的如果只传入两个参数,并且第二个参数是函数而不是对象的时候,此时直接进行替换赋值。并且当检测到第三个参数enhancer不为空并且符合规范的时候,直接执行
enhancer(createStore)(reducer, preloadedState)
这一段其实是为之后的中间件进行服务的
然后是定义了一系列变量:currentState,currentListener,nextListener等。然后就开始依次实现store暴露的几个函数
getState函数很简单,只是返回currentState而已

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

subscribe函数的实现是通过nextListener数组来实现对当前listeners来进行增删,之所有需要一个currentListener又需要一个nextListener,是因为如果直接在currentListener上进行修改,会导致由于数组是复杂类型,一旦进行更改会使整个数组发生改变。

    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    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()
    }

上面是dispatch的核心源码,去掉了一些类型检测,这个函数主要是通过设置了一个isDispatching 标志位,来判断传入的action是否已经被监听过,否则则直接调用reducer函数,并且将listener数组都执行一遍
再继续往下看,replaceReducer也很简单,略过

combineReducers

这部分的代码很长,但是大部分都是在对参数做一些校验处理,核心源码是下面几行

const finalReducerKeys = Object.keys(finalReducers)
...
   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
  }

主要的过程如下:

  • 检验传递进来的参数reducer是否是合法的,然后将合法的reducer放进finalReducer中,并获取对应的key
  • 通过一个for循环,实现对应的reducer获取对应key值的state
  • 返回改变过的state
    通过上面的实现,那么下面两种写法也是等价的
第一种:
function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}

第二种:
const todoApp = combineReducers({
  visibilityFilter,
  todos
})

bindActionCreators

这个函数的作用,主要是弱化了store.dispatch的作用,直接在bindActionCreators中进行封装了,源码如下:

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
  }
  • 首先是判断传入的actionCreators这个参数是function还是一系列function数组
  • 如果是function,那就直接执行bindActionCreator,可以看到bindActionCreator中实现了dispatch的功能
  • 如果传入的是数组,则通过for循环对每一个进行上一步的操作

作用:自动把action创建函数绑定到dispatch中,省略通过store.dispatch()进行手动dispatch

applyMiddleware

这是唯一一个很绕的API,虽然代码很简洁,下面对它进行分析:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}
  • 首先通过调用createStore来生成一个store,并且获取到dispatch,定义了一个空数组chain,
  • storedispatch作为参数传入middleware中,先来看看中间件的统一格式:
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };
}

上面这个格式是采用了ES6的语法格式,解析应该是下面这样:

    return function (next) {
        return function (action) {
            return next(action);
        }
    }
}
  • middleware经过处理后pushchain数组
  • composechain数组进行处理
    先来看看compose的实现源码:
export default function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose底层调用的是Array.prototype.reduceRight
举例:
Dispatch = compose([fn1,fn2,fn3])(store.dispatch)会被解析成如下所示:
dispatch = f1(f2(f3(store.dispatch)),也就是f3的返回值作为f2的参数,f2返回值作为f1的参数

  • 通过compose处理后返回return next(action)
    其实是如下:
function(action){
      if (typeof action === 'function') {
           return action(dispatch, getState, extraArgument);
      }
      return next(action);
 }

这个也是包装后的store.dispatch,与原先的store.dispatch不同,通过这种方式一直进行传递

为什么middlewareApi中要使用这一行dispatch: (action) => dispatch(action) ,而不是直接dispatch:dispatch
因为store.dispatch 是一直在改变的,并且需要获取到最新的store.dispatch,这一句正是实现这个功能,每次都可以获取最新的,而不是最先的那个store.dispatch

至此,完结