redux源码分析程序员WEB前端程序开发

深入理解Redux之源码解读

2017-02-08  本文已影响747人  ronniegong

去年在几个react-native项目中使用了redux,当时由于理解有限,仅限于实现了功能,代码很糟糕。最近补了下课,试图深入理解Redux。

1.Redux简介

简单来说,Redux是一个应用状态管理工具。本文不涉及Redux基础使用,为更好的理解Redux,引用了一张图来描述Redux数据流程。


上图中可以看出,Redux中几个主要内容包括ActionReducerStoreStateMiddleware。用户在应用视图层触发的Action,经由层层Middleware,到达Reducer中,经过Reducer的处理,产生新的State。这一过程在Store中发生,最终新的State被用来刷新应用视图,完成一次交互。

2.Redux源码结构

本文代码解读基于redux版本3.6.0版本,源码结构如下


3.createStore.js

3.1 代码讲解

createStore代码结构清晰,代码主要内容为

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

/**
 * ActionTypes里定义的是Redux保留的私有action。
 * 对于任何未知的action,你必须返回store的当前状态。
 * 如果传入的当前状态是undefined,你必须返回store的初始状态。
 * 不要在应用代码中直接引用这些action。
 */
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

/**
 * createStore方法用于创建一个保存程序状态的store。
 * 改变store中数据的唯一方法是调用store的`dispatch()`方法。
 *
 * 你的应用中应该只有一个store。为了将程序状态中不同部分的变更逻辑
 * 组合在一起,你可以通过`combineReducers`方法将多个reducer组合成一个reducer。
 *
 * @param {Function} reducer 一个返回应用下一状态的函数,入参是程序的当前状态以及
 * 要发送的action。
 *
 * @param {any} [preloadedState] store的初始状态。你可以选择性的为store指定一个
 * 初始状态。
 * 如果你使用了`combineReducers`方法来生成最终的reducer。那么这个初始状态对象的
 * 结构必须与调用`combineReducers`方法时传入的参数的结构保持对应关系。
 *
 * @param {Function} enhancer store增强器。你可以选择性的传入一个增强函数来扩展
 * store的功能,例如中间件,时间旅行,持久化等。Redux自带的唯一一个增强器是
 * `applyMiddleware()`方法。
 *
 * @returns {Store} 返回一个可以读取状态,发送action以及订阅变更通知的Redux store。
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 如果只传入reducer和enhancer,则store的初始状态为undefined
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // enhancer必须是一个函数
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    // 返回使用enhancer增强后的store
    return enhancer(createStore)(reducer, preloadedState)
  }


  // reducer必须是一个函数
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

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

  // 在每次修改监听函数数组之前复制一份,实际的修改发生在这个新
  // 复制出来的数组上。确保在某次dispatch发生前就存在的监听器,
  // 在该次dispatch之后都能被触发一次。
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * 读取store状态
   *
   * @returns {any} 返回store的当前状态
   */
  function getState() {
    return currentState
  }

  /**
   *
   * 新增一个变更监听函数。每当dispatch了一个action之后监听函数都会被触发一次。
   * 你可以在监听函数中通过`getState()`方法获取store的最新状态。
   *
   * 你可以在一个回调函数中再次调用`dispatch()`。但需要注意以下两点:
   *
   * 1. 在每一次dispatch()调用执行之前,监听函数数组都会被复制一份(通过前文提到
   * 的ensureCanMutateNextListeners方法)。如果你在监听函数中增加或删除其他监听函
   * 数,那么这些操作并不会影响到当前进行中的这一轮dispatch。而下一次dispatch,
   * 不论其是否是嵌套调用,都会使用最新的,修改后的监听函数列表。
   *
   * 2. 由于在一个监听函数执行前可能已经进行了多次嵌套的dispatch调用,因此不能保证
   * 每个监听函数都可以获取到所有的状态变更。然而,可以确定的是,在某次dispatch
   * 触发之前已经注册的监听函数都可以读取到这次diapatch之后store的最新状态。
   *
   * @param {Function} listener 每次dispatch之后执行的回调函数
   * @returns {Function} 返回一个用于取消这次订阅的函数
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true

    // 添加监听函数前确保只操作当前数组的一份拷贝
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      // 移除监听函数前确保只操作当前数组的一份拷贝
      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * 发送一个action。这是触发状态变更的唯一方法。
   *
   * 每次发送action的时候,用于创建store的`reducer`函数都会被调用一次。调用时
   * 传入的参数是当前的状态以及被发送的`action`。调用的返回值会被当做更新后的
   * 状态。调用完成后,所有的状态监听函数都会被触发。
   *
   * 基础实现中仅支持发送形式为简单对象的action。如果你希望可以发送Promise,Observable,
   * thunk或是其它形式的action,你需要使用相应的中间件把store创建函数封装起来。
   * 你可以在`redux-thunk`模块的文档中找到这方面的示例。不过即使在这些中间件内部
   * 还是通过dispatch方法发送了简单对象形式的action。
   *
   * @param {Object} action 一个表示变更内容的对象。保证你的action是可被序列化的
   * 是一种很好的实践,这样你就可以记录并回放用户的操作,或是使用可以穿梭时间
   * 的`redux-devtools`插件。一个action必须有一个值不为`undefined`的`type`属性。
   * 推荐你使用字符串常量来表示action类型。
   *
   * @returns {Object} 为了方便起见,返回你传入的action对象。
   *
   * 要注意的是,如果你使用了一个自定义的中间件,它可能会把`dispatch()`的返回值
   * 封装成其它内容(比如,一个你可以await的Promise)
   */
  function dispatch(action) {
    // 如果action不是简单对象,抛出异常
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    // 如果action的类型是undefined,抛出异常
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    // reducer内部不允许再次调用dispatch,否则抛出异常
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    // 执行reducer,传入当前状态和action
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 触发所有的状态监听回调函数
    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }

  /**
   * 替换store当前使用的reducer函数
   * 
   * 如果你的应用程序实现了代码拆分并且你希望动态加载某些reducer的时候你
   * 可能会用到这个方法。或者当你要为Redux实现一个热加载机制的时候,你也
   * 会用到它。
   *
   * @param {Function} nextReducer 要使用的新reducer
   *
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

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

  /**
   * 为observable/reactive库预留的交互接口。
   * @returns {observable} 表示状态变更的最简单的observable对象
   * 想要获取更多信息,可以查看observable提案:
   * https://github.com/zenparsing/es-observable
   */
  function observable() {
    // 首先保留对Redux中subscribe方法的引用,在observable的世界里
    // 有一个同名方法。
    var outerSubscribe = subscribe
    return {
      /**
       * 一个极简的observable订阅方法。
       * @param {Object} observer 任何可以作为observer使用的对象
       * observer对象应该包含一个`next`方法。
       * @returns {subscription} 返回一个带有`unsbscribe`方法的对象。该
       * 方法将用于停止接收来自store的状态变更信息。
       */
      subscribe(observer) {
        // observer参数必须是一个对象,否则抛出异常。但并未检测是否有next方法。。。
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        // 创建一个状态变更回调函数。逻辑很简单,把store最新的状态传给observer
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        // 立即执行一次回调函数,把当前状态传给observer
        observeState()
        var unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },
      // 根据observable提案,[Symbol.observable]()返回observable对象自身
      [$$observable]() {
        return this
      }
    }
  }

  // store创建好以后,立即发送一个初始化action。这样做是为了让reducer
  // 返回store的初始状态(当给reducer传入的当前state为undefined时,reducer
  // 会返回store的初始状态)。

  dispatch({ type: ActionTypes.INIT })

  // 返回创建好的store对象
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

3.2 使用示例

createStore创建一个store实例,实例保存了应用的状态,提供了对外API,以一个简单的例子进行说明

//action creator
var addTodo = function(text){
    return {
        type: 'add_todo',
        text: text
    };
};

// reducer方法, 传入的参数有两个
// state: 当前的state
// action: 当前触发的行为, {type: 'xx'}
// 返回值: 新的state
function TodoReducer(state = [], action){
    switch (action.type) {
        case 'add_todo':
            return state.concat(action.text);
        default:
            return state;
    }
};

// 创建store, 传入两个参数
// 参数1: reducer 用来修改state
// 参数2(可选): [], 默认的state值,如果不传, 则为undefined
var store = redux.createStore(TodoReducer,[]);

// 通过 store.getState() 可以获取当前store的状态(state)
// 默认的值是 createStore 传入的第二个参数
store.getState();  // state is []

// 通过 store.dispatch(action) 来达到修改 state 的目的
// 注意: 在redux里,唯一能够修改state的方法,就是通过 store.dispatch(action)
store.dispatch({type: 'add_todo', text: '读书'});

// 修改后的state
store.getState();  // state is ['读书']

4.combineReducers.js

上面使用示例中的,仅有一个reducer。实际使用中,一般会按业务拆分出多个reducer,combineReducers的作用在于合并多个reducer函数,并返回一个新的reducer函数。

4.1 代码讲解

import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'

const NODE_ENV = typeof process !== 'undefined' ? process.env.NODE_ENV : 'development'

function getUndefinedStateErrorMessage(key, action) {
    const actionType = action && action.type
    const actionName = (actionType && `"${actionType.toString()}"`) || 'an action'

    return (
        `Given action ${actionName}, reducer "${key}" returned undefined. ` +
        `To ignore an action, you must explicitly return the previous state. ` +
        `If you want this reducer to hold no value, you can return null instead of undefined.`
    )
}

function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
    const reducerKeys = Object.keys(reducers)
    const argumentName = action && action.type === ActionTypes.INIT ?
        'preloadedState argument passed to createStore' :
        'previous state received by the reducer'

    if (reducerKeys.length === 0) {
        return (
            'Store does not have a valid reducer. Make sure the argument passed ' +
            'to combineReducers is an object whose values are reducers.'
        )
    }

    if (!isPlainObject(inputState)) {
        return (
            `The ${argumentName} has unexpected type of "` +
            ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
            `". Expected argument to be an object with the following ` +
            `keys: "${reducerKeys.join('", "')}"`
        )
    }

    const unexpectedKeys = Object.keys(inputState).filter(key =>
        !reducers.hasOwnProperty(key) &&
        !unexpectedKeyCache[key]
    )

    unexpectedKeys.forEach(key => {
        unexpectedKeyCache[key] = true
    })

    if (unexpectedKeys.length > 0) {
        return (
            `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
            `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
            `Expected to find one of the known reducer keys instead: ` +
            `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
        )
    }
}
//reducer合法性检查
function assertReducerSanity(reducers) {
    Object.keys(reducers).forEach(key => {
        const reducer = reducers[key]
        const initialState = reducer(undefined, { type: ActionTypes.INIT })

        if (typeof initialState === 'undefined') {
            throw new Error(
                `Reducer "${key}" returned undefined during initialization. ` +
                `If the state passed to the reducer is undefined, you must ` +
                `explicitly return the initial state. The initial state may ` +
                `not be undefined. If you don't want to set a value for this reducer, ` +
                `you can use null instead of undefined.`
            )
        }

        const type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
        if (typeof reducer(undefined, { type }) === 'undefined') {
            throw new Error(
                `Reducer "${key}" returned undefined when probed with a random type. ` +
                `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
                `namespace. They are considered private. Instead, you must return the ` +
                `current state for any unknown actions, unless it is undefined, ` +
                `in which case you must return the initial state, regardless of the ` +
                `action type. The initial state may not be undefined, but can be null.`
            )
        }
    })
}

/**
 * 将值为各个不同reducer方法的对象,转化为一个单一reducer方法。
 * 它将调用所有子reducer方法,并将结果合并到一个state对象。
 * 该state对象的key和reducer方法名一一对应
 *
 * @param {Object} reducers 值为各个不同reducer方法的对象
 *
 * @returns {Function} 单reducer方法.
 */
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 (NODE_ENV !== 'production') {
            if (typeof reducers[key] === 'undefined') {
                warning(`No reducer provided for key "${key}"`)
            }
        }

        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }
    const finalReducerKeys = Object.keys(finalReducers)

    let unexpectedKeyCache
    if (NODE_ENV !== 'production') {
        unexpectedKeyCache = {}
    }

    let sanityError
    try {
        // 对所有的子reducer 做一些合法性断言,如果没有出错再继续下面的处理
        assertReducerSanity(finalReducers)
    } catch (e) {
        sanityError = e
    }

    //返回单reducer方法,参数和普通reducer方法一致,分别为state对象和action对象
    return function combination(state = {}, action) {
        if (sanityError) {
            throw sanityError
        }

        if (NODE_ENV !== 'production') {
            const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
            if (warningMessage) {
                warning(warningMessage)
            }
        }

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

4.2 使用示例

假设有两个reducer

function TodoReducer(state, action) {}
function FilterReducer(state, action) {}

通过调用combineReducers(),返回了一个新的wrapReducers

var wrapReducers = redux.combineReducers({
    todos: TodoReducer,
    filter: FilterReducer
});

将wrapReducers传入createStore()

var store = redux.createStore(wrapReducers,{});

注意,这里传入初始state对象时,需要和wrapReducers对象结构对应。
store中的currentReducer为wrapReducers,在store.dispatch(action)时,执行wrapReducers(state,action),进而依次执行所有子Reducer方法。根据action.type匹配Reducer中的switch case。

5.bindActionCreator.js

5.1 代码讲解

//使用dispatch包裹actionCreator
function bindActionCreator(actionCreator, dispatch) {
    return (...args) => dispatch(actionCreator(...args))
}

/**
 * 将值为action creator的对象,转换为相同的key,但是方法被包裹到dispatch调用中,使得方法可以被直接调用的对象。
 *
 * @param {Function|Object} actionCreators 值为action creator的对象
 *
 * @param {Function} Redux store的dispatch方法
 *
 * @returns {Function|Object} 相同的key,方法被包裹到dispatch调用中的对象
 */
export default function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }

    if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
            `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
            `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
        )
    }

    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
}

5.2 使用示例

假设有如下action creator

var addTodo = function(text){
    return {
        type: 'add_todo',
        text: text
    };
};

使用bindActionCreators前,这样调用

store.dispatch({type: 'add_todo', text: '读书'});

使用bindActionCreators包裹addTodo

var actions = redux.bindActionCreators({
    addTodo: addTodo
}, store.dispatch);

现在可以这样调用

actions.addTodo('看电影');

bindActionCreators是一个语法糖,非必须。他的作用在于,将redux相关api--dispatch,封装在调用内部。这样,在实际应用中,从顶层容器,向子组件以props传递这些action creator时,子组件可以以普通函数的形式直接调用这些action creator,对redux无感知。

6.applyMiddleware.js

6.1 代码讲解

6.1.1 applyMiddleware

import compose from './compose'

/**
 * 创建一个增强的store,可以应用middleware来dispatch store的方法
 *
 * 每一个middleware会议命名参数形式传入`dispatch` 和 `getState`方法
 *
 * @param {...Function} middlewares 被调用的中间件链.
 * @returns {Function} 应用中间件的增强store.
 */
export default function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, preloadedState, enhancer) => {
        //创建store
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []

        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        //初始化所有中间件,传入middlewareAPI,`dispatch` 和 `getState`方法
        //这里的dispatch方法是一个封装的方法,
        //注意,此dispatch 不等于 store.dispatch,compose之后,dispatch已变更
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        
        //使用compose将中间件组合成调用链,传入原始store.dispatch作为钩子,执行最终的store.dispatch
        dispatch = compose(...chain)(store.dispatch)

        //注意,返回的store,dispatch不再是原始store.dispatch
        return {
            ...store,
            dispatch
        }
    }
}

6.1.2 compose

/**
 * 将方法从右到左组合起来
 * compose(f, g, h)被转换为(...args) => f(g(h(...args)))
 *
 * @param {...Function} funcs 需要被组合的方法.
 * @returns {Function} 将方法从右到左组合起来. 
 */

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

6.2 使用示例

理解applyMiddleware需要结合middleware
假设有两个middleware,分别为thunkMiddleware、logger

function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}
function logger({ dispatch, getState }) {
    return next => action => {
        console.log('logger: dispatching ' + action.type);
        var result = next(action);
        console.log('logger: next state ' + result);
        return result;
    }
}

调用applyMiddleware(thunkMiddleware,logger),执行middlewares.map(middleware => middleware(middlewareAPI))后,完成了中间件的初始化,chain为一个数组,包含两个函数

继续执行compose(...chain)(store.dispatch);,dispatch方法为

其中,next为


var result = next(action)中的next为store.dispatch。
中间件和store.dispatch组成了一个调用链,thunkMiddleware(logger(store.dispatch(action)))

当有如下异步action creator

function asyncAddTodo(text) {
        return dispatch => {
            setTimeout(() => {
                // Yay! Can invoke sync or async actions with `dispatch`
                dispatch(addTodo(text));
        }, 1000);
        };
    }

applyMiddleware()返回的增强型store,执行store.dispatch(asyncAddTodo('test')),因为返回的是一个func : dispatch => {...},所以先执行该func。当异步返回时,执行dispatch(addTodo(text));,此处的dispatch为前述调用链thunkMiddleware(logger(store.dispatch(action))),这时代码依次往下走,最终通过原始的store.dispatch,触发redux自身disptach(action),进而进入Reducer继续执行。

参考文章

如果觉得有帮助,可以扫描二维码对我打赏,谢谢

上一篇 下一篇

猜你喜欢

热点阅读