Redux源码解读

Oct 25, 2015

821

最近在弄公司管理后台,考虑到只是在团队里面用,尝试采用React来写,状态采用Redux来管理,结合webpack开发。

在React中,所谓的组件就是一个个状态机器,当应用变得复杂时,状态如果管理不当,会使代码变得难以调试。Redux通过限制更新发生的时间和方式,尝试使state的变化变得可预测。Redux是一个很小的库,除了能与React一起使用,还支持其它库,如angular。

Redux可描述为如下三大原则:

  1. 单一数据源,整个应用只有一个store;
  2. state是只读的,只能通过触发action来修改;
  3. 通过编写reducers来描述action如何修改state。

接下来,通过Redux源码来了解Redux如何工作。

Redux仅有以下几个API,以Redux3.0.0为例

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
};

首先,来看一下createStore。简化后的源码如下:

 export var ActionTypes = {
  INIT: '@@redux/INIT'
};

export default function createStore(reducer, initialState) {
  var currentReducer = reducer;
  var currentState = initialState;
  var listeners = [];
  var isDispatching = false;

  function getState() {
    return currentState;
  }

  //订阅,返回取消订阅的接口
  function subscribe(listener) {
    listeners.push(listener);

    return function unsubscribe() {
      var index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  //触发action
  function dispatch(action) {
    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    listeners.slice().forEach(listener => listener());
    return action;
  }

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

  //第一次触发,用于初始化store树
  dispatch({ type: ActionTypes.INIT });

  return {
    dispatch, //更新state
    subscribe, //注册监听器
    getState, //获取state
    replaceReducer //更新reducer
  };
}

createStore类似于发布订阅模式,主要用于连接action和reducers。reducer是纯函数,用于指明应用如何更新state。整个Redux应用只有一个单一的store,当需要拆分逻辑时,就需要组合reducer,那接下来就来看combineReducers。

import { ActionTypes } from '../createStore';
import isPlainObject from '../utils/isPlainObject';
import mapValues from '../utils/mapValues';
import pick from '../utils/pick';

export default function combineReducers(reducers) {
  //过滤reducers,返回key-value object形式的reducers
  var finalReducers = pick(reducers, (val) => typeof val === 'function');

  //检测状态是否正确
  Object.keys(finalReducers).forEach(key => {
    var reducer = finalReducers[key];
    //返回默认状态
    if (typeof reducer(undefined, { type: ActionTypes.INIT }) === 'undefined') {
      throw new Error();
    }

    var type = Math.random().toString(36).substring(7).split('').join('.');
    if (typeof reducer(undefined, { type }) === 'undefined') {
      throw new Error();
    }
  });

  //返回默认state,key对应的value为undefined
  var defaultState = mapValues(finalReducers, () => undefined);

  //返回一个组合函数,参数为state和action。
  return function combination(state = defaultState, action) {
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var newState = reducer(state[key], action);
      if (typeof newState === 'undefined') {
        throw new Error('');
      }
      return newState;
    });

    return finalState;
  };
}

reducer在返回时不能修改旧的state,建议使用Object.assign()创建一个副本,且第一个参数为{},这样子就不会修改state。同时,在default情况下返回旧的state。

actions用于描述事情发生了这种事实,是store数据的唯一来源,通过store.dispatch()将action传递到store。action创建函数就是创建action的方法,只是纯函数,返回action对象而已。为了避免多次手动绑定action创建函数到dispatch()上,bindActionCreators用于自动把多个action创建函数绑定到dispatch()上。

import mapValues from '../utils/mapValues';

//将action创建函数绑定到dispatch上
function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args));
}

//将多个action创建函数绑定到disptach上
export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch);
  }

  if (typeof actionCreators !== 'object' || actionCreators === null || actionCreators === undefined) {  // eslint-disable-line no-eq-null
    throw new Error('');
  }

  return mapValues(actionCreators, actionCreator =>
    bindActionCreator(actionCreator, dispatch)
  );
}

有了action、reducer、store这三样东西,我们就可以来理解Redux的单向数据流了。

根reducer将多个子reducer输出合成一个单一的state树 => store调用传入的reducer函数 => store保存了根reducer返回的完整state树。

当调用store.dispatch(action)时,根据action.type执行reducer并返回state,所有的监听器都将被调用。

接下来,看一下applyMiddleware接口,applyMiddleware接口用于包装dispatch()来达到我们想要的目的,比如打日志、异步请求等。

compose函数用于从右向左组合函数。

那么,理解了Redux中的API以及数据流向,再使用react-redux去进行开发,对react中state树的管理便会清晰很多。

JavaScript 」相关文章

Wen's Blog

文章归档 » 文章标签 » 博主:吴文伟,Web开发爱好者,专注于前端开发,该博客用于记录和分享平时遇到的一些问题以及知识。

订阅

联系方式

链接