redux的reateStore,combineReducers,bindActionCreators,applyMiddleware源码分析

最新内容,请在github阅读。同时,All issue and star welcomed!

1.深入分析redux中的createStore方法

我们首先以一个官方demo来说,代码如下:

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import Counter from './components/Counter'
import counter from './reducers'
const store = createStore(counter)
const rootEl = document.getElementById('root')
const render = () => ReactDOM.render(
  <Counter
    value={store.getState()}
    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
  \/>,
  rootEl
)
render()
store.subscribe(render)

下面我们来看看createStore的作用:

const store = createStore(counter)
//下面就是counter函数
export default (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

createStore表示我们创建一个数据库,这个数据库计算下一个状态的方式是通过counter来完成的。而且,如果你要计算下一个状态,需要给我传入当前状态,以及你需要的特定的操作的名称,即Action。同时,这个数据库也提供了很多其他的方法。

    getState: 获取当前的数据库的状态

    subscribe: 当你对数据库发出一个指令,而且数据库根据这个指令已经计算得到新的状态
    以后需要执行的回调函数

    dispatch:发出一个Action,告诉数据库你要干嘛。数据库会根据当前的状态以及你的命令类型计算得到新的状态。计算完成以后,我们要执行subscribe添加的所有的回调函数.

    replaceReducer:用一个新的store替换掉我们当前的store用来计算我们的新的state。如果你的app支持code splitting, 而且你想要动态加载reducer。同时如果你要为你的Redux支持Hot Reloading也需要

    observable:

其中observable的内容如下:

 function observable() {
    var outerSubscribe = subscribe
    return {
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }
        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }
        observeState()
        var unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },
      [$$observable]() {
        return this
      }
    }
  }

该方法的实现不是本文讨论的重点,你可以查看文末的参考资料。此时我们回到上面的源代码。

第一步:createStore构建数据库并传入计算新state的规则

const store = createStore(counter)

数据库会有上面的getState,subscribe,dispatch,replaceReducer,observable等各种方法。你可以给它注册事件,也可以从它哪里获取到数据。

第二步:注册回调函数。当数据库收到指令,并且通过它计算到新的值的时候重新render。也就是说,每次dispatch导致数据库状态变化后我们都会重新render

store.subscribe(render)

第三步:渲染render页面

const render = () => ReactDOM.render(
  <Counter
    value={store.getState()}
    //获取数据库状态
    onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
    //向数据库发送指令
    onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
  \/>,
  rootEl
)
render()

2.深入分析redux中的combineReducers方法

首先贴上源码(关键部分):

export default function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  var finalReducers = {}
  //对每一个reducer进行循环,将那些导出是函数的reducer保存到finalReducers中
  for (var i = 0; i < reducerKeys.length; i++) {
    var 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]
    }
  }
  var finalReducerKeys = Object.keys(finalReducers)
  //得到我们关注的reducer所有的键名
  if (NODE_ENV !== 'production') {
    var unexpectedKeyCache = {}
  }
  var sanityError
  try {
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }
  //此时我们的combineReducers返回一个函数了,也就是最终结合起来的reducer
  //和普通的reducer一样,接收state与action为两个参数
  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }
    if (NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
      if (warningMessage) {
        warning(warningMessage)
      }
    }
    var hasChanged = false
    var nextState = {}
    //http://redux.js.org/docs/api/combineReducers.html
    //此时key为["todos","counter"]
    //store.dispatch({
    //   type: 'ADD_TODO',
    //   text: 'Use Redux'
    // })
    // 此时表示我们会要reducers去处理,但是具体让那个reducer处理是要我们自己选择的~~
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      //此时如todos,counter两个key
      var reducer = finalReducers[key]
      //得到某个key对应的具体的reducer函数。如上面这种情况key和value是一致的,为todos和counter
      var previousStateForKey = state[key]
      //对于每一个reducer只会处理相应的key,所以我们的state肯定是如下的格式
      //{todos:[],counter:0},也就是我们 ${ActionTypes.INIT}的初始值
      var nextStateForKey = reducer(previousStateForKey, action)
      //第一次循环如果是todos,那么我们会获取state中的todos的值以及action传入计算得到新的todos值
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      //更新这个key的值
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      //这个state是否发生变化
    }
    //如果没有一个属性的值发生变化,那么hasChanged就是为false
    return hasChanged ? nextState : state
  }
}

我们真实使用的使用是如下方式的:

  import todos from './todos'
  import counter from './counter'
  export default combineReducers({
    todos,
    counter
  })

你应该知道,我们这里导出的调用combineReducers后的结果其实就是我们组合后的reducer函数,这个函数会被传入到createStore中,就像以前我们传入单个reducer函数到createSStore中一样。对于每一个reducer函数的编写也是有具体的要求的:

function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key]
    var initialState = reducer(undefined, { type: ActionTypes.INIT })
    //通过对我们的reducer执行ActionTypes.INIT,得到这个reducer的初始值
    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.`
      )
    }

    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    //我们的每一个reducer传入的第一个参数是state,第二个参数是Action
   //(1)不要处理@@redux这个空间下的Action或者@@redux下的${ActionTypes.INIT},他们是私有的
   //(2)对于我们未知的action我们必须返回当前的state状态,除非这个Action是undefined.
   //   此时因为action是undefined,所以我们返回初始值,而不管Action本身的类型
    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.`
      )
    }
  })
}

注意三点:通过对我们的reducer执行ActionTypes.INIT,得到这个reducer的初始值;不要处理@@redux这个空间下的Action或者@@redux下的${ActionTypes.INIT},他们是私有的;对于我们未知的action我们必须返回当前的state状态,除非这个Action是undefined, 此时因为action是undefined,所以我们返回初始值(不是当前值),而不管Action本身的类型。还有一点需要注意:

  for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      //此时如todos,counter两个key
      var reducer = finalReducers[key]
      //得到某个key对应的具体的reducer函数。如上面这种情况key和value是一致的,为todos和counter
      var previousStateForKey = state[key]
      //对于每一个reducer只会处理相应的key,所以我们的state肯定是如下的格式
      //{todos:[],counter:0},也就是我们 ${ActionTypes.INIT}的初始值
      var nextStateForKey = reducer(previousStateForKey, action)
      //第一次循环如果是todos,那么我们会获取state中的todos的值以及action传入计算得到新的todos值
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      //更新这个key的值
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
      //这个state是否发生变化
    }

对于这个for循环来说,传入一个Action的情况下,我们对将两个reducer函数(todos与counter)都执行它。对于不处理这个Action.type的reducer,我们会直接根据reducer返回当前的值,即currentState。同时,我们也必须注意,我们返回的state肯定是如下的类型,即肯定包含todos与counter属性,即使是获取初始${ActionTypes.INIT}值( nextState[key] = nextStateForKey):

{
  todos:[],
  counter:0
}

3.深入分析redux中的bindActionCreators方法

我们直接给出源代码:

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}
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)
  //获取keys
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    //得到我们的actionCreator方法
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
    //boundActionCreators["removeTodo"]=function(){}
    //boundActionCreators["addTodo"]=function(){}
    //所以,返回的内容很简单,就是把我们自己的函数包装一下,返回一个新的函数
    //当你调用这个新的函数的时候,新函数会直接dispatch我们的旧函数调用的值
    //同时将新函数的参数也传递给旧函数
  }
  return boundActionCreators
}

代码很简单,我们先给出调用的例子:

 export function addTodo(text) {
  return {
    type: 'ADD_TODO',
    text
  }
}
//通过bindActionCreators包裹的函数必须是action creators
export function removeTodo(id) {
  return {
    type: 'REMOVE_TODO',
    id
  }
}
let boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)

所以,返回的内容很简单,就是把我们自己的函数包装一下,返回一个新的函数
当你调用这个新的函数的时候,新函数会直接**dispatch我们的旧函数调用后的值
同时将新函数的参数也传递给旧函数**。所以上面的例子得到的数据类型如下:

{
  addTodo:function(){},
  removeTodo:function(){}
  //这里的function是对我们的函数进行了一次包裹
}

4.深入分析redux中的applyMiddleware方法

直接贴上源码:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
 //如果中间件的个数为1,那么获取第一个中间件
  if (funcs.length === 1) {
    return funcs[0]
  }
  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  //last表示最后一个中间件,而rest标签除了最后一个中间件的其他中间件
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  //初始值为h(...args),reduceRight第一个函数的第一个参数为previousValue
}
//For example, compose(f, g, h) is identical to doing
//(...args) => f(g(h(...args))).

下面是applyMiddleware源码:

export default function applyMiddleware(...middlewares) {
  //createStore=>reducer, preloadedState, enhancer
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    //得到一个store
    var dispatch = store.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)
    // (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
    //[middleware1,middleware2,middleware3]
    // middleware1(middleware2(middleware3(last(store.dispatch))))
    return {
      ...store,
      dispatch
    }
  }
}

下面是一个middleware的中间件的例子:

 function logger({ getState }) {
      return (next) => (action) => {
        console.log('will dispatch', action)
        // Call the next dispatch method in the middleware chain.
        let returnValue = next(action)
        console.log('state after dispatch', getState())
        // This will likely be the action itself, unless
        // a middleware further in chain changed it.
        return returnValue
      }
    }

从源码中我们可以看到,中间件的执行是分为下面三个步骤的:

第一步:调用我们middleware,传入getState与dispatch

  var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }

第二次:再次调用我们的middleware,传入store.dispatch

    dispatch = compose(...chain)(store.dispatch)

第三次:不管什么时候调用,调用方式如何,只要传入action即可。这也是我们唯一需要自己控制的一步~

  return {
      ...store,
      dispatch
    }

还有一点需要注意,我们真实调用applyMiddleware时候的方式是:

let store = createStore(
  todos,
  [ 'Use Redux' ],
  applyMiddleware(logger)
)

很显然,我们上面的applyMiddleware第二级函数参数是createStore,那么是什么时候调用的呢?我们看看createStore方法(只是摘取关键部分演示):

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    //applyMiddleware(logger)此处继续传入createStore与reducer,preloadedState
    //同时return直接返回了。但是我们要知道,在applyMiddleWare中我们创建了自己的
    //store,即 var store = createStore(reducer, preloadedState, enhancer)
    return enhancer(createStore)(reducer, preloadedState)
  }
}

是不是明白了,在createStore的时候我们传入了applyMiddleware(logger)。那么后面我们在createStore里面继续传入了createStore与reducer, preloadedState等函数

5.redux的数据流

参考资料:

zen-observable

proposal-observable

symbol-observable

redux-observable

ServerRendering

redux-observable.js.org

从零开始搭建React同构应用(三):配置SSR

redux官网

applyMiddleware的参数为什么加展开符?

源码分析仓库

发布了210 篇原创文章 · 获赞 83 · 访问量 98万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章