Redux源码浅析

339 阅读5分钟

Redux作为当前应用非常广泛的一个库,包含了很多值得学习的地方,对源码有所了解自然也能更好的进行使用。 首先让我们从最常用的createStore开始

createStore(reducers, [initState], [enhancer])

createStore作为最基础的方法其代码并不复杂,它创建了一个闭包用于存放reducer, state, listeners等内容,并暴露这些变量的操作方法,从而完成store的创建。 每个store包含了4个方法,dispatch, subscribe, getState, replaceReducer, 其实还有一个方法 observable ,这个方法主要用于observable/reactive的库,用于监听state的变化,详见

redux中最强大的莫过于enhancer了,它允许对创建或修改store对象,以及改变其默认的行为。applymiddlewares , redux-devtools 均是通过enhancer对redux进行了加强,因此redux本身,它的逻辑很轻,解耦解的很彻底。

创建后的store对象含有4个方法:

  • dispatch: 发送一个action至reducer中用于更新当前的state, 并在reducer执行完成后调用当前store中存储的监听器。
  • getState: 获取当前最新的state状态。
  • subscribe: 添加一个监听器,并返回一个用于解除监听的回调函数。
  • replaceReducer: 替换当前的reducer。

reducers 应该是我们平时接触得最多的一个内容了,它本身其实只是一个方法,当dispatch被调用时,将action及当前的state对象传入到reducer中,从而获取到新的state对象值,从而使整个redux形成一个数据流。createStore本身并不提供非方法的reducer支持,因此需要借助 combineReducers 的力量,将state进行拆分从而进行对象的分割。不过对于store来说,reducer始终只是一个方法。

subscribe 用于添加一个监听state变化的listener,当有action被dispatch以后,listener便会被触发。

官方文档中提到,一个 subscribe 不应该注意到所有 state 的变化,在编译器被调用之前,由于 dispatch 的嵌套,state 可能已经发生了多次的改变,因此并不是每次dispatch方法都会引起该方法被执行。

其次监听器在运行时还有一点需要注意,监听器列表会在执行前被浅拷贝一份,因此监听器运行期间的subscribe/unsubscribe方法的执行并不会影响到本次的运行。

接下来是applyMiddlware,相信下面这段代码大家应该不陌生

import thunk from 'redux-thunk';
import logger from 'redux-logger';
import reducer from './reducer';

createStore(reducer, applyMiddleware(thunk, logger));

redux-thunk redux-logger 这些库都是middleware的实现,来看看applyMiddleware方法吧。

applyMiddleware

我们先来看下最简单的middleware代码模板

// api {object}
// api.getState {function} 获取当前的state状态
// api.dispatch {function} 发送一个action
// 
// next:下一个middleware的实际逻辑,需要将action传入
// action: redux的action对象
const middleware = api => next => (action) => {
    // do something
}

middleware的模版是用来创建一个自定义middleware。middlware主要用来对dispatch方法进行扩展及自定义。

applyMiddleware主要的有效代码集中在下面两行

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

return {
    ...store,
    dispatch
};

从上面的代码中可以看到所有的middleware通过第一次方法的执行会被传入一个API对象,对象内包含了getState与dispatch函数。 由于每个middleware中传入了相同的API对象,因此对象中的方法其实是可以被 复写 的,可以实现自己的dipsatch方法以实现相关逻辑。

由于第一次执行时,通过api对象传入的dispatch会被复写并嵌套,因此可以看作是store的dispatch,是整个chain的起点。 后续具体的实现中,需要不断的将上一次的运行结果传递下去,因此许通过compose方法将所有的方法连接起来产生嵌套关系,例如 m1(m2(m3(m4(...args)))),这时,整个chain的顺序是逆向的(从后到前),此时通过执行chain可以很好的将redux的dispatch方法传递给最后一个middleware实现,并构建起正确的顺序。

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

compose本质上就是一个方法数组的reduce,依据顺序将后一个方法执行的结果传递给前一个,第一个方法最后执行。

compose由于只是一种工具方法,因此它相较于 applyMiddleware 并不仅限于某种特定的场景。

compose示例,createStore的enhancer使用

// enhancer
const enhancer = (createStore) => (reducer, initState) => {
    // #1: createStore
    const store = createStore(reducer, initState);
    const { dispatch } = store;
    const dispatchProxy = (action, ...args) => {
        console.log(`before doing action ${action.type}`);
        dispatch(action, ...args);
        console.log(`after doing action ${action.type}`);
    };

    return {
        ...store,
        dispatch: dispatchProxy,
    }
};

const store = createStore(reducer, compose(enhancer, applyMiddleware()));

示例代码中的enhancer便是一个使用compose场景,代码中对store的dispatch方法做了扩展。每次调用dispatch时,会在调用前及调用后输出一段日志,记录当前执行的action类型。

对于示例代码中 #1 处的createAction其实并不是redux本身的createAction,它是由 applyMiddleware 生成的。 所以 enhancer 中的dispatch等方法,其实已经经过了 applyMiddlware 的扩展。

bindActionCreators

这个方法用的比较少,该方法接收两个参数 actionCreators , dispatch

其中 actionCreator 是一个 Map<string, function> 对象,dispatch 方法对应store的dispatch方法 当前方法会遍历 actionCreator 并返回一个新的map对象,新map对象中的所有方法都会替换为由下面这段方法创建的一个proxy

proxy用于将原方法的执行结果直接传递给 dispatch 方法。

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