redux源码解析

用npm安装redux,看目录结构
npm install redux
找到src目录
* index.js: redux主文件,主要对外暴露核心api
* createStore.jscreateStore方法的定义
* utils: applyMiddleware、combineReducers、bindActionCreators为redux的核心方法,pick、mapValue、compose为普通的工具函数

    ├── createStore.js
    ├── index.js
    └── utils
        ├── applyMiddleware.js
        ├── bindActionCreators.js
        ├── combineReducers.js
        ├── compose.js
        ├── isPlainObject.js
        ├── mapValues.js
        └── pick.js

index.js

import createStore from './createStore';
import combineReducers from './utils/combineReducers';
import bindActionCreators from './utils/bindActionCreators';
import applyMiddleware from './utils/applyMiddleware';
import compose from './utils/compose';

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

createStore.js

先翻译一下createStore方法的注释

    /**
     * Creates a Redux store that holds the state tree.
     * The only way to change the data in the store is to call `dispatch()` on it.
     *
     * There should only be a single store in your app. To specify how different
     * parts of the state tree respond to actions, you may combine several reducers
     * into a single reducer function by using `combineReducers`.
     *
     * @param {Function} reducer A function that returns the next state tree, given
     * the current state tree and the action to handle.
     *
     * @param {any} [preloadedState] The initial state. You may optionally specify it
     * to hydrate the state from the server in universal apps, or to restore a
     * previously serialized user session.
     * If you use `combineReducers` to produce the root reducer function, this must be
     * an object with the same shape as `combineReducers` keys.
     *
     * @param {Function} enhancer The store enhancer. You may optionally specify it
     * to enhance the store with third-party capabilities such as middleware,
     * time travel, persistence, etc. The only store enhancer that ships with Redux
     * is `applyMiddleware()`.
     *
     * @returns {Store} A Redux store that lets you read the state, dispatch actions
     * and subscribe to changes.
     */

创建一个redux store来管理状态树,如果你想改变状态树上的数据,只能通过调用dispatch发起一个action。在你的web app中,有且只能有一个store。为了说明状态树对action的响应,你应该把所有reducer通过一个combineReducers合并成一个单一的reducer函数(关于reducer可以参考我另一篇博文redux文档解读)。这里的参数reducer是一个接收当前状态树和action,返回下一个状态树的函数。参数preloadedState指的是初始状态,你可以将它与服务器的状态合并(比如ajax)。参数enhancer是store的加强功能,你可以选择通过第三的东西比如中间件、持久化等来加强store的功能,唯一一个使用方法是applyMiddleware()。函数返回值是一个redux store,可以让你读取状态树上的state,也可以让你dispatch action来改变状态。所以这个createStore方法, 传入了reducer、initialState,并返回一个store对象。
先看一下redux的初始化

      // When a store is created, an "INIT" action is dispatched so that every
      // reducer returns their initial state. This effectively populates
      // the initial state tree.
      dispatch({ type: ActionTypes.INIT })

当一个store创建的时候,会先dispatch一个action,type为ActionTypes.INIT(上文中有定义),这时候reducer会把他们初始的state返回,构成了初始的状态树
再来看一下比较核心的dispatch方法

      /**
       * Dispatches an action. It is the only way to trigger a state change.
       *
       * The `reducer` function, used to create the store, will be called with the
       * current state tree and the given `action`. Its return value will
       * be considered the **next** state of the tree, and the change listeners
       * will be notified.
       *
       * The base implementation only supports plain object actions. If you want to
       * dispatch a Promise, an Observable, a thunk, or something else, you need to
       * wrap your store creating function into the corresponding middleware. For
       * example, see the documentation for the `redux-thunk` package. Even the
       * middleware will eventually dispatch plain object actions using this method.
       *
       * @param {Object} action A plain object representing “what changed”. It is
       * a good idea to keep actions serializable so you can record and replay user
       * sessions, or use the time travelling `redux-devtools`. An action must have
       * a `type` property which may not be `undefined`. It is a good idea to use
       * string constants for action types.
       *
       * @returns {Object} For convenience, the same action object you dispatched.
       *
       * Note that, if you use a custom middleware, it may wrap `dispatch()` to
       * return something else (for example, a Promise you can await).
       */
      function dispatch(action) {
        if (!isPlainObject(action)) {
          throw new Error(
            'Actions must be plain objects. ' +
            'Use custom middleware for async actions.'
          )
        }

        if (typeof action.type === 'undefined') {
          throw new Error(
            'Actions may not have an undefined "type" property. ' +
            'Have you misspelled a constant?'
          )
        }

        if (isDispatching) {
          throw new Error('Reducers may not dispatch actions.')
        }

        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
      }

再翻译下注释,action只支持plain object,即用JSON形式定义的普通对象或者new Object()创建的简单对象。如果想dipatch一个Promise,一个thunk,你需要包装你的store create函数。可以看一看redux-thunk的文档,当然最后middleware还是使用这个方法diapatch了plain object。action用来表示改变的是什么,可以把action序列化, 便于记录。action必须有一个type属性,而且这个属性不能是undefined。最好是把action的属性赋值为大写的字符串常量。
dispatch主要是几个判断,action和action.type的判断,当dipatch的时候,改变函数内的全局变量isDispatching,然后把currentState修改为reducer返回的state,最后调用subscribe中传入的回调函数也就是监听器listener。
下面讲一下subscribe函数

    /**
       * Adds a change listener. It will be called any time an action is dispatched,
       * and some part of the state tree may potentially have changed. You may then
       * call `getState()` to read the current state tree inside the callback.
       *
       * You may call `dispatch()` from a change listener, with the following
       * caveats:
       *
       * 1. The subscriptions are snapshotted just before every `dispatch()` call.
       * If you subscribe or unsubscribe while the listeners are being invoked, this
       * will not have any effect on the `dispatch()` that is currently in progress.
       * However, the next `dispatch()` call, whether nested or not, will use a more
       * recent snapshot of the subscription list.
       *
       * 2. The listener should not expect to see all state changes, as the state
       * might have been updated multiple times during a nested `dispatch()` before
       * the listener is called. It is, however, guaranteed that all subscribers
       * registered before the `dispatch()` started will be called with the latest
       * state by the time it exits.
       *
       * @param {Function} listener A callback to be invoked on every dispatch.
       * @returns {Function} A function to remove this change listener.
       */
      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()
          //通过闭包拿到listener的值
          var index = nextListeners.indexOf(listener)
          //在listeners数组中除去这个lisnter
          nextListeners.splice(index, 1)
        }
      }

这里调用了ensureCanMutateNextListeners方法

    //初始化监听器数组
    var currentListeners = []
    var nextListeners = currentListeners
    function ensureCanMutateNextListeners() {
        if (nextListeners === currentListeners) {
          nextListeners = currentListeners.slice()
        }
    }

添加一个变化监听器,listener是我们调用时传入的回调函数,listener会在action被dispatch之后被调用,你可以在回调函数listener里调用 getState() 来拿到当前 state。
如果需要解绑这个变化监听器,执行 subscribe 返回的函数即可。在实际使用中:

    function handleChange(){
        store.getState()...
    }
    let unsubscribe = store.subscribe(handleChange)
    unsubscribe()

接下来是replaceReducer

    /**
       * Replaces the reducer currently used by the store to calculate the state.
       *
       * You might need this if your app implements code splitting and you want to
       * load some of the reducers dynamically. You might also need this if you
       * implement a hot reloading mechanism for Redux.
       *
       * @param {Function} nextReducer The reducer for the store to use instead.
       * @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 })
      }

替换 store 当前用来计算 state 的 reducer,并且dispatch初始化的action。最后返回的store对象,具有dispatch,subscribe,getState,replaceReducer方法

    return {
        dispatch,
        subscribe,
        getState,
        replaceReducer
      };

combineReducers.js

先看核心的combineReducers

    /**
     * Turns an object whose values are different reducer functions, into a single
     * reducer function. It will call every child reducer, and gather their results
     * into a single state object, whose keys correspond to the keys of the passed
     * reducer functions.
     *
     * @param {Object} reducers An object whose values correspond to different
     * reducer functions that need to be combined into one. One handy way to obtain
     * it is to use ES6 `import * as reducers` syntax. The reducers may never return
     * undefined for any action. Instead, they should return their initial state
     * if the state passed to them was undefined, and the current state for any
     * unrecognized action.
     *
     * @returns {Function} A reducer function that invokes every reducer inside the
     * passed object, and builds a state object with the same shape.
     */
    export default function combineReducers(reducers) {
      var reducerKeys = Object.keys(reducers)
      var finalReducers = {}
      for (var i = 0; i < reducerKeys.length; i++) {
        var key = reducerKeys[i]

        if (process.env.NODE_ENV !== 'production') {
          if (typeof reducers[key] === 'undefined') {
            warning(`No reducer provided for key "${key}"`)
          }
        }

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

      if (process.env.NODE_ENV !== 'production') {
        var unexpectedKeyCache = {}
      }

      var sanityError
      try {
        // 对所有的子reducer 做一些合法性断言
        assertReducerSanity(finalReducers)
      } catch (e) {
        sanityError = e
      }

      return function combination(state = {}, action) {
        if (sanityError) {
          throw sanityError
        }

        if (process.env.NODE_ENV !== 'production') {
          var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
          if (warningMessage) {
            warning(warningMessage)
          }
        }

        var hasChanged = false
        var nextState = {}
        for (var i = 0; i < finalReducerKeys.length; i++) {
          var key = finalReducerKeys[i]
          var reducer = finalReducers[key]
          var previousStateForKey = state[key]
          var nextStateForKey = reducer(previousStateForKey, action)
          if (typeof nextStateForKey === 'undefined') {
            var errorMessage = getUndefinedStateErrorMessage(key, action)
            throw new Error(errorMessage)
          }
          nextState[key] = nextStateForKey
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
      }
    }   

先翻译下注释,先将一个reducers对象(value为不同的reducer函数),转化成唯一一个reducer函数,它可以调用原来的每一个reducer函数,并且将他们的结果合并到一个单一的状态对象。调用这个函数只需要使用es6的语法import即可。此外这个函数永远不会返回undefined,redux 首次执行时,state 为 undefined,它将会返回我们设置的初始state。

    const initialState = {
         ...
    };
    //使用es6的默认值语法
    function todoApp(state = initialState, action) {
      ...
      return state
    }

而为什么我们之前将reducer分解为多个子reducer函数,是为了对不同的状态对应相应的reducer,并且更新时使用

    //Object.assign实现局部更新,只对state的变化部分进行更新
    return Object.assign({}, todos, {
      completed: !todos.completed
    })

从combineReducers的代码上看,先是使用finalReducers对象对reducers对象中的键值对进行保存,遍历reducers对象,检查其中的value值是否为函数,是函数的话加入finalReducers对象, 当key值为undefined的时候,非生产环境会提示警告。assertReducerSanity函数检查我们是否在子reducer函数中定义了initialState。finalReducers的对象key为reducer函数名,value为reducer函数。所以遍历这个对象,对应每一个state(下面场景中的state.todos,state.filter)对应的reducer函数进行执行,最后将结果保存到nextState。
调用这个函数的场景如下:

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

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

bindActionCreator.js

    /**
     * Turns an object whose values are action creators, into an object with the
     * same keys, but with every function wrapped into a `dispatch` call so they
     * may be invoked directly. This is just a convenience method, as you can call
     * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
     *
     * For convenience, you can also pass a single function as the first argument,
     * and get a function in return.
     *
     * @param {Function|Object} actionCreators An object whose values are action
     * creator functions. One handy way to obtain it is to use ES6 `import * as`
     * syntax. You may also pass a single function.
     *
     * @param {Function} dispatch The `dispatch` function available on your Redux
     * store.
     *
     * @returns {Function|Object} The object mimicking the original object, but with
     * every action creator wrapped into the `dispatch` call. If you passed a
     * function as `actionCreators`, the return value will also be a single
     * function.
     */
    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"?`
        )
      }

      var keys = Object.keys(actionCreators)
      var boundActionCreators = {}
      for (var i = 0; i < keys.length; i++) {
        var key = keys[i]
        var actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
          boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
      }
      return boundActionCreators
    }

翻译下注释,将一个对象(value是action的creator),变成一个key相同,但是每个函数都被包进了dispatch。这是一个快捷方式,总比你调用store.dispatch(MyActionCreators.doSomething())快得多。参数dispatch就是createStore扔出来的store的dispatch方法。
这个js的核心是bindActionCreator函数

    function bindActionCreator(actionCreator, dispatch){
        //...args是actionCreator和dispatch,相当于给actionCreator函数再包裹了一层dispatch
        return (...args) => dispatch(actionCreator(...args))
    }

可以通过一个例子来理解

    var addTodo = function(text){
        return {
            type: 'add_todo',
            text: text
        };
    };
    var actions = redux.bindActionCreators({
        addTodo: addTodo
    }, store.dispatch);
    //相当于dispatch一个{ type: 'add_todo', text: 'xx' }
    actions.addTodo('xx');

applyMiddleware.js

    /**
     * Creates a store enhancer that applies middleware to the dispatch method
     * of the Redux store. This is handy for a variety of tasks, such as expressing
     * asynchronous actions in a concise manner, or logging every action payload.
     *
     * See `redux-thunk` package as an example of the Redux middleware.
     *
     * Because middleware is potentially asynchronous, this should be the first
     * store enhancer in the composition chain.
     *
     * Note that each middleware will be given the `dispatch` and `getState` functions
     * as named arguments.
     *
     * @param {...Function} middlewares The middleware chain to be applied.
     * @returns {Function} A store enhancer applying the middleware.
     */
    export default function applyMiddleware(...middlewares) {
      return (createStore) => (reducer, preloadedState, enhancer) => {
        var store = createStore(reducer, preloadedState, enhancer)
        var dispatch = store.dispatch
        var chain = []

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

        return {
          ...store,
          dispatch
        }
      }
    }

继续先翻译,创建一个store的增强件,它可以引进一些中间件给dispatch方法,这个对于很多任务都很方便,比如说异步的action,或者记录每一个action。
再结合官方文档对middleware的解释,这里提供的是action被发起后,到达reducer之前的扩展点,你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。在调用store.dispatch前后想记录被发起的action和新的state,第一种方法如下:

    store.dispatch(addTodo('xx'));
    //第一种方法
    let action = addTodo('xx');
    console.log('dispatching', action)
    store.dispatch(action)
    console.log('next state', store.getState())
    //第二种方法,封装
    function dispatchAndLog(store, action) {
      console.log('dispatching', action)
      store.dispatch(action)
      console.log('next state', store.getState())
    }
    dispatchAndLog(store, addTodo('Use Redux'))
    //第三种方法,用一个中间变量保存store.dispatch
    let next = store.dispatch
    store.dispatch = function dispatchAndLog(action){
        console.log('dispatching', action)
        let result = next(action)
        console.log('next state', store.getState())
        return result
    }

    //第四种方法
    function logger(store) {
      let next = store.dispatch

      // 我们之前的做法:
      // store.dispatch = function dispatchAndLog(action).. 
      return function dispatchAndLog(action) {
        console.log('dispatching', action)
        let result = next(action)
        console.log('next state', store.getState())
        return result
      }
    }
    function applyMiddlewareByMonkeypatching(store,middlewares){
          middlewares = middlewares.slice()
          middlewares.reverse()

          // 在每一个 middleware 中变换 dispatch 方法。
          middlewares.forEach(middleware =>
            store.dispatch = middleware(store)
          )
    }
    applyMiddlewareByMonkeypatching(store, [ logger, crashReporter ])

第四种方法在middleware的数组内部的回调函数中改变了store.dispatch方法。那么为什么我们要替换原来的dispatch呢,主要是因为每一个middleware都想使用包装过的store.dispatch,在这边例子中就是我们包装过的日志记录函数。如果 applyMiddlewareByMonkeypatching 方法中没有在第一个 middleware 执行时立即替换掉 store.dispatch,那么 store.dispatch 将会一直指向原始的 dispatch 方法。也就是说,第二个 middleware 依旧会作用在原始的 dispatch 方法。
但是,还有另一种方式来实现这种链式调用的效果。可以让 middleware 以方法参数的形式接收一个 next() 方法,而不是通过 store 的实例去获取。

    function logger(store) {
      return function wrapDispatchToAddLogging(next) {
        return function dispatchAndLog(action) {
          console.log('dispatching', action)
          let result = next(action)
          console.log('next state', store.getState())
          return result
        }
      }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值