redux源码解读

  react在作大型项目的时候,前端的数据通常会愈来愈复杂,状态的变化难以跟踪、没法预测,而redux能够很好的结合react使用,保证数据的单向流动,能够很好的管理整个项目的状态,可是具体来讲,下面是redux的一个核心流程图:前端

        

  即整个项目的数据存储在Store中,每一个状态下Store会生成一个state,一个state对应着一个view,而用户只能接触到view,不能接触到store,那咱们怎么才能让store中的数据发生改变呢? 因此,必需要经过view来间接改变,即用户点击,产生action,不能点击一次,建立一个action,因此须要一个action creator,而后将这个action经过dispatch函数送到store中,这个过程当中,可使用一些中间件来处理一些异步操做,而后将数据交给store,store拿到数据以后,经过reducer来根据不一样的action的type来处理数据,返回一个新的state,经过新的state,就能够再产生一个新的view了。 而且能够看到 store、view、action这样的一个单项数据流。 react

    

  为了更好地理解redux,咱们能够读一下redux的源码。 webpack

 

  首先,咱们将redux源码获得,总体目录以下:git

        

   而redux源码的核心固然是处在src中的,dist、es、lib都不是最重要的,因此,咱们展开src,能够看到下面的目录:github

        

  下面主要说一下总体:web

  • utils下的warning.js文件用于控制台错误日志输出,能够忽略。
  • index.js 为入口文件,看源码时应该首先看这个文件。
  • createStore是用于建立store的,因此是主流程,也是比较重要的一环。 
  • 其余四个文件 --- applyMiddlewares.js、 bindActionCreators.js、combineReducers.js、compose.js这四个文件属于助攻型文件。

 

入口文件 index.js

  

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

/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === \'production\'. ' +
    'This means that you are running a slower development build of Redux. ' +
    'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
    'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
    'to ensure you have the correct code for your production build.'
  )
}

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

咱们能够看到: 在index.js中,主要是从主流程文件、几个辅助api文件以及日志打印文件中获取了接口,而后中间是一些环境方面的警告,能够忽略,最后就经过这个index文件导出了全部咱们在redux使用的过程当中所须要的api,因此入口文件index.js就是起了一个桥梁了做用,很是简单,可是很重要。编程

 

 

主流程文件: createStore.js

  createStore.js主要是用于生成store的,咱们还能够从最后暴露的对象看出其暴露了几个方法:redux

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

  也就是说,这个主流程文件整个就是在定义了这么几个方法,下面看卡源码(被精简了,只留下重要部分):api

// 这个action必定是会被最早触发的,从redux-devtools就能够看得出来。
export const ActionTypes = {
  INIT: '@@redux/INIT'
}

// 接受三个参数
// 第一个参数是必须的,reducer,用来根据给定的状态和action来返回下一个状态树,进一步致使页面发生变化。
// 第二个参数是一个状态,这个不是必须的,大部分时候不须要。
// 第三个参数是enhancer,一般是一个中间件,好比使用redux-devtools这个中间件。
export default function createStore(reducer, preloadedState, enhancer) {

  // 当前的reducer  
  let currentReducer = reducer

  // 当前的状态。
  let currentState = preloadedState

  // 能够看出,这个是一个订阅者,state有变化,须要告诉这些Listeners。 
  let currentListeners = []

  // 后续的listeners,是不断更新的。
  let nextListeners = currentListeners

  // 是否dispatch。
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 获取当前的state树
  function getState() {
    return currentState
  }

  /**

   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  
  // 接收的是一个函数做为参数, 这个函数会在每一次dispatch的时候被调用。
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    // 取消订阅
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  // 惟一触发state改变的方式。 dispatch a action.
  // 这里是dispatch的一个基本的实现,只能提供一个普通的对象。 若是你但愿dispatch一个Promise、thunk、obserbable等,你须要包装你的store
  // 建立函数进入一个相应的中间件,如 redux-thunk。  
  // 这个action必定要包含type类型。最后也会返回这个action
  function dispatch(action) {

    // 若是正在dispatch,则抛出错误。
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}数组

// dispatch的过程当中,每个listener都会被调用。 由于在subscribe中传入的参数就是一个listener函数。
    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替换一个新的reducer
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }

  // observable函数
  function observable() {
    const 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()
        const unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }
   
  // store被建立,就会有一个INIT action被处罚,因此每一个reducer就会返回他们的初始值了。 
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

getState方法很是简单,就是获取当前的store中的state,不用过多赘述。

replaceReducer方法也很简单, 就是简单的替换reducer。 

 

 


 

其中,subscribe用于注册监听事件,而后返回取消订阅的函数,把全部的订阅事件都放在了一个订阅数组里,只要维护这个数组就行了,subscribe的做用就是这么简单。 

每次dispatch的时候就会依次调用数组中的监听事件。

store.subscribe()方法总结:

  • 入参函数放入监听队列

  • 返回取消订阅函数

    


 

  

再看看dispatch方法,dispatch是触发state改变的惟一方式,最为核心的就是下面的这段代码了:

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = currentListeners = nextListeners
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

这段代码中,首先,将isDispatching设置为了true,而后就调用currentReducer返回了一个新的currentState, 这样就成功了改变了当前的状态,最后, 改变了状态以后,就开始把subscribe函数中注册的事件开始以此执行。OK! 到这里,dispatch方法就比较清楚了。

因此,这里对dispatch方法作一个总结:

  • 调用reducer, 传入参数(currentState, action)。
  • 按顺序执行订阅的listener。
  • 返回action。 

 

ok! 至此,主流程文件就已经分析完了,总体仍是比较简单的,比较重要的一个函数就是dispatch,但也是很好理解的。

 

下面,主要讲一讲剩下的几个辅助文件:

bindActionCreators.js

bindActionCreators把action creators转成拥有同名keys的对象,使用dispatch把每一个action creator包装起来,这样能够直接调用它们。

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"?`
    )
  }

  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
}

实际状况用到的并很少,唯一的应用场景是当你须要把action creator往下传到一个组件上,却不想让这个组件觉察到Redux的存在,并且不但愿把Redux Store或dispatch传给它。

 

 


 

CombineReducers.js

  这个文件中暴露的方法是咱们经常使用的,由于在写reducer的时候,每每须要根据类别不一样,写多个reducer,可是根据createStore能够知道,只有一个reducer能够被传入,因此这里的combineReducers就是为了将多个reducer合并成一个reducer的。具体源码以下(通过精简以后,就只剩下30多行了):

// 接受一个对象做为参数,这个对象的值是须要被combine的不一样的reducer函数。
// 返回一个函数, 这个函数就是一个大的reducer了。
export default function combineReducers(reducers) {
  // 获取reducer的全部的keys数组。
  const reducerKeys = Object.keys(reducers)
  // 最终的reducer对象。
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    // 将全部的reducer从新放在finalReducers中,至关于浅拷贝。
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // 获取最终的全部的reducers数组。
  const finalReducerKeys = Object.keys(finalReducers)

  // 返回了一个函数,能够看出这个函数和咱们一个一个定义的reducer函数是相似的,因此,这就是一个大的reducer函数。  
  return function combination(state = {}, action) {
    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)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

因此这个combineReducers仍是很明确的,就是将全部的reducer组合成一个大的。

 


 

compose.js

  这个函数用于组合传进来的一系列函数,在中间件的时候会用到,能够看到,执行的最终结果就是把一系列函数串联起来:

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

  在中间件的时候会用到这个函数

 


applyMiddleware.js

  这个函数用于 store 加强。 

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

  用法大体以下:

const store = createStore(reducer,applyMiddleware(…middlewares))
or
const store = createStore(reducer,{},applyMiddleware(…middlewares))

  

  好比一个比较经常使用的redux-thunk中间件,源码的关键代码以下:

function createThunkMiddleware(extraArgument) {
  return function (_ref) {
    var dispatch = _ref.dispatch,
        getState = _ref.getState;
    return function (next) {
      return function (action) {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }

        return next(action);
      };
    };
  };
}

 

 

  做用的话能够看到,这里有个判断:若是当前action是个函数的话,return一个action执行,参数有dispatch和getState,不然返回给下个中间件。这种写法就拓展了中间件的用法,让action能够支持函数传递。即若是action是一个函数,那么咱们就能够进一步来处理了,若是这个action是一个对象,说明就要直接又dispatch来触发了,即这里的action其实是在真正的dispatch以前所作的一些工做。 

 

 


 

 

通常,咱们认为redux属于函数式编程,即函数是第一等公民、数据是不可变的(在reducer中,咱们但愿每次返回一个新的state,而不是修改旧的state,而后返回,因此这里强调的就是不可变的)、有肯定的输入就有肯定的输出。   总体来讲,可能redux不是纯纯的函数式编程,可是也比较符合函数式编程的风格了。

以下:

const arr = [1, 2, 3];

arr.push(4); //这样很差,看到这代码我就方了,须要从上往下琢磨一下arr到底存的是啥
const newArr = [...arr, 4]; //这样,arr不会被修改,很放心,要修改过的版本用newArr就行了

 

 

以下:

const me = {name: 'Morgan'};

me.skill = 'React'; //这样很差,拿不许me里是啥了
const newMe = {...me, skill: 'React'}; //这样,me不会被修改
相关文章
相关标签/搜索