Redux详解(三)之Redux 源码实现

241 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情 

createStore

使用

实现之前先分析如何使用

// 实例化
const store = createStore(reducer, preloadedState, enhancer)
store.dispatch(action);
store.subscribe(() => {
  // 获取store对象中存储的状态
 const allState = store.getState()
})

综上createStore 须有三个实例方法 subscribe, dispatch, getState 回顾使用方法。

实现

1. subscribe

subscribe 其实就是一个收集方法的过程,收集所有调用subscribe传进去的回调函数

let currentListeners = []
//store.subscribe(callback)
function subscribe (listener) {
  currentListeners.push(listener);
}

2. dispatch

dispatch后相当于把action发送给reducer,由reducer来根据action.type 处理不同的逻辑,最后返回最新的state,同时调用收集到的listener()


function dispatch(action){
    currentState = reducer(currentState, action)
    // 遍历所有的订阅者,并调用回调函数。
    for (let i = 0; i < currentListeners.length; i++) {
        // const listener = currentListeners[i]
        // listener()
        // 省略为下面写法
        currentListeners[i]()
    }
}

3. getState

getState 也就是获取所有状态,dispatch方法调用reducer获取到的state 我们直接赋值给createStore方法中的变量currentState,调用getState()直接将currentState变量返回即可

function getState(state) {
    return currentState
}

增加类型判断逻辑后最终createStore代码如下

function createStore (reducer, preloadedState, enhancer) {
  // reducer 类型判断 
  if (typeof reducer !== 'function') {
    throw new Error(
      `Expected the root reducer to be a function. Instead, received: '${kindOf(
        reducer
      )}'`
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('enhancer必须是函数')
    }
    return enhancer(createStore)(reducer, preloadedState);
  }
  var currentState = preloadedState;
  var currentListeners = [];
  function getState () {
    return currentState;
  }
  function dispatch (action) {
    // 判断action是否是一个对象
    if (!isPlainObject(action)) {
      throw new Error('action为对象');
    }
    if (typeof action.type === 'undefined') {
        throw new Error('action对象中必须有type属性');
     }
    // 调用reducer函数 处理状态
    currentState = reducer(currentState, action);
    // 调用订阅的回调函数,通知订阅者
    for (var i = 0; i < currentListeners.length; i++) {
      var listener = currentListeners[i];
      listener();
    }
  }

 function subscribe (listener) {
    currentListeners.push(listener);
  }

  // 默认调用一次dispatch方法 存储初始状态,同时也是在初始化的时候提前抛出以上校验的错误
  dispatch({type: 'initAction'})

  return {
    getState,
    dispatch,
    subscribe
  }
}

applyMiddleware

使用

applyMiddleware 就是注册中间件。

const store = createStore(rootReducer, applyMiddleware(logger, sagaMiddleware))

这里我们可能有疑问

  1. createStore实例化的时候第二个参数是preloadedState而不是Middleware?
  2. 多个中间件调用顺序?

问题1 我们查看createStore源码可以看到一下逻辑, 如果第二个参数为方法,并且没有第三个参数,我们就会把第二个参数赋值给enhancer,第二个参数当undefined

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  enhancer = preloadedState
  preloadedState = undefined
}

问题二 倒序调用。

回顾 首先createStore中参数

enhancer = applyMiddleware(logger, sagaMiddleware)

然后createStore中执行enhancer 相当于执行了

enhancer(createStore)(reducer, preloadedState) 等价于下面代码

applyMiddleware(logger, sagaMiddleware)(createStore)(reducer, preloadedState)

中间件的定义

export default store => next => action => {
    console.log(store.getState(), action)
    next(action)
}

createStore遇到enhancer直接返回。所以applyMiddleware 首先创建store

实现

/**
 * applyMiddleware(logger, sagaMiddleware)(createStore)(reducer, preloadedState)
 *
 * export default store => next => action => {
 *     console.log(store.getState(), action)
 *     next(action)
 * }
 * */
function applyMiddleware (...middlewares) {
    return function (createStore) {
        // 所以最终相当于执行了下面函数
        return function (reducer, preloaded) {
            const store = createStore(reducer, preloaded)
            /*
            * 执行中间件的第一层后剩余
            *  next => action => {
            *     console.log(store.getState(), action)
            *     next(action)
            *   }
            * */
            const chain = middlewares.map(middleware => middleware(store));
            // 执行后面两层要传递next === dispatch 第三层直接传action,相当于直接调dispatch(action)
            const dispatch = compose(...chain)(store.dispatch);

            return {
                ...store,
                dispatch
            }

        }

    }


}

function compose () {
    const functionArray = [...arguments];
    return function (dispatch) {
        for (let i = functionArray.length - 1; i >= 0; i--) {
            // 每次调用第二层后
            dispatch = functionArray[i](dispatch);
        }
        return dispatch;
    }
}

bindActionCreators

使用

实际就是将对象{type: "increment"} 转化为dispatch({type: "increment"})

const actions = bindActionCreators({increment, decrement}, store.dispatch);

function increment () {
  return {type: "increment"}
}

function decrement () {
  return {type: "decrement"};
}

实现

function bindActionCreators (actionCreators, dispatch) {
  let boundActionCreators = {};
  for (let key in actionCreators) {
    // 之所以用自执行函数而不是直接赋值给boundActionCreators对象,
    //是因为for循环定义完成后,在执行的时候key是确定的,
    // 相当于所有的action最终都是最后执行的action,所以用自执行函数保持key
    (function (key) {
      boundActionCreators[key] = function () {
        dispatch(actionCreators[key]())
      }
    })(key)
  }
  return boundActionCreators;
}

combineReducers

使用

counterReducer counter里面的逻辑函数

modalReducer modal弹框的逻辑函数

combineReducers是以key:function形式为对象作为参数

const rootReducer = combineReducers({counter: counterReducer,modal: modalReducer})

所以我们要做的就是把两个function合并成一个function,自然返回的state根据上面的key变成打的对象newState{ counter: {count: 1}, modal: {modalVisible: false} }

实现

function combineReducers (reducers) {
  const reducerKeys = Object.keys(reducers);
  for (let i = 0; i < reducerKeys.length; i++) {
    // 获取reducer的逻辑处理函数,并判断是否为函数
    let key = reducerKeys[i];
    if (typeof reducers[key] !== 'function') {
        throw new Error('reducer必须是函数');
    }

  return function (state, action) {
    var nextState = {};
    for (var i = 0; i < reducerKeys.length; i++) {
      var key = reducerKeys[i];
      // couterReducer modalReducer
      var reducer = reducers[key];
      var previousStateForKey = state[key];
      nextState[key] = reducer(previousStateForKey, action)
    }
    return nextState;
  }
}

总结,redux代码特别是中间件的思想还是很值得学习的。