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这样的一个单项数据流。

    

  为了更好地理解redux,我们可以读一下redux的源码。

  首先,我们将redux源码得到,整体目录如下:

   而redux源码的核心当然是处在src中的,dist、es、lib都不是最重要的,所以,我们展开src,可以看到下面的目录:

  下面主要说一下整体:

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

入口文件 index.js

  

  1. import createStore from './createStore'
  2. import combineReducers from './combineReducers'
  3. import bindActionCreators from './bindActionCreators'
  4. import applyMiddleware from './applyMiddleware'
  5. import compose from './compose'
  6. import warning from './utils/warning'
  7.  
  8. /*
  9. * This is a dummy function to check if the function name has been altered by minification.
  10. * If the function has been minified and NODE_ENV !== 'production', warn the user.
  11. */
  12. function isCrushed() {}
  13.  
  14. if (
  15. process.env.NODE_ENV !== 'production' &&
  16. typeof isCrushed.name === 'string' &&
  17. isCrushed.name !== 'isCrushed'
  18. ) {
  19. warning(
  20. 'You are currently using minified code outside of NODE_ENV === \'production\'. ' +
  21. 'This means that you are running a slower development build of Redux. ' +
  22. 'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
  23. 'or DefinePlugin for webpack (http://stackoverflow.com/questions/30030031) ' +
  24. 'to ensure you have the correct code for your production build.'
  25. )
  26. }
  27.  
  28. export {
  29. createStore,
  30. combineReducers,
  31. bindActionCreators,
  32. applyMiddleware,
  33. compose
  34. }

我们可以看到: 在index.js中,主要是从主流程文件、几个辅助api文件以及日志打印文件中获取了接口,然后中间是一些环境方面的警告,可以忽略,最后就通过这个index文件导出了所有我们在redux使用的过程中所需要的api,所以入口文件index.js就是起了一个桥梁了作用,非常简单,但是很重要。

主流程文件: createStore.js

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

  1. return {
  2. dispatch,
  3. subscribe,
  4. getState,
  5. replaceReducer,
  6. [$$observable]: observable
  7. }

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

  1. // 这个action一定是会被最先触发的,从redux-devtools就可以看得出来。
  2. export const ActionTypes = {
  3. INIT: '@@redux/INIT'
  4. }
  5.  
  6. // 接受三个参数
  7. // 第一个参数是必须的,reducer,用来根据给定的状态和action来返回下一个状态树,进一步导致页面发生变化。
  8. // 第二个参数是一个状态,这个不是必须的,大部分时候不需要。
  9. // 第三个参数是enhancer,通常是一个中间件,比如使用redux-devtools这个中间件。
  10. export default function createStore(reducer, preloadedState, enhancer) {
  11.  
  12. // 当前的reducer
  13. let currentReducer = reducer
  14.  
  15. // 当前的状态。
  16. let currentState = preloadedState
  17.  
  18. // 可以看出,这个是一个订阅者,state有变化,需要告诉这些Listeners。
  19. let currentListeners = []
  20.  
  21. // 后续的listeners,是不断更新的。
  22. let nextListeners = currentListeners
  23.  
  24. // 是否dispatch。
  25. let isDispatching = false
  26.  
  27. function ensureCanMutateNextListeners() {
  28. if (nextListeners === currentListeners) {
  29. nextListeners = currentListeners.slice()
  30. }
  31. }
  32.  
  33. // 获取当前的state树
  34. function getState() {
  35. return currentState
  36. }
  37.  
  38. /**
  39.  
  40. * @param {Function} listener A callback to be invoked on every dispatch.
  41. * @returns {Function} A function to remove this change listener.
  42. */
  43.  
  44. // 接收的是一个函数作为参数, 这个函数会在每一次dispatch的时候被调用。
  45. function subscribe(listener) {
  46. if (typeof listener !== 'function') {
  47. throw new Error('Expected listener to be a function.')
  48. }
  49.  
  50. let isSubscribed = true
  51.  
  52. ensureCanMutateNextListeners()
  53. nextListeners.push(listener)
  54.  
  55. // 取消订阅
  56. return function unsubscribe() {
  57. if (!isSubscribed) {
  58. return
  59. }
  60.  
  61. isSubscribed = false
  62.  
  63. ensureCanMutateNextListeners()
  64. const index = nextListeners.indexOf(listener)
  65. nextListeners.splice(index, )
  66. }
  67. }
  68.  
  69. // 唯一触发state改变的方式。 dispatch a action.
  70. // 这里是dispatch的一个基本的实现,只能提供一个普通的对象。 如果你希望dispatch一个Promise、thunk、obserbable等,你需要包装你的store
  71. // 创建函数进入一个相应的中间件,如 redux-thunk。
  72. // 这个action一定要包含type类型。最后也会返回这个action
  73. function dispatch(action) {
  74.  
  75. // 如果正在dispatch,则抛出错误。
  76. if (isDispatching) {
  77. throw new Error('Reducers may not dispatch actions.')
  78. }

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

  1. // dispatch的过程中,每一个listener都会被调用。 因为在subscribe中传入的参数就是一个listener函数。
  2. const listeners = currentListeners = nextListeners
  3. for (let i = ; i < listeners.length; i++) {
  4. const listener = listeners[i]
  5. listener()
  6. }
  7.  
  8. return action
  9. }
  10.  
  11. // 替换一个新的reducer
  12. function replaceReducer(nextReducer) {
  13. currentReducer = nextReducer
  14. dispatch({ type: ActionTypes.INIT })
  15. }
  16.  
  17. // observable函数
  18. function observable() {
  19. const outerSubscribe = subscribe
  20. return {
  21. subscribe(observer) {
  22. if (typeof observer !== 'object') {
  23. throw new TypeError('Expected the observer to be an object.')
  24. }
  25.  
  26. function observeState() {
  27. if (observer.next) {
  28. observer.next(getState())
  29. }
  30. }
  31.  
  32. observeState()
  33. const unsubscribe = outerSubscribe(observeState)
  34. return { unsubscribe }
  35. },
  36.  
  37. [$$observable]() {
  38. return this
  39. }
  40. }
  41. }
  42.  
  43. // store被创建,就会有一个INIT action被处罚,所以每个reducer就会返回他们的初始值了。
  44. dispatch({ type: ActionTypes.INIT })
  45.  
  46. return {
  47. dispatch,
  48. subscribe,
  49. getState,
  50. replaceReducer,
  51. [$$observable]: observable
  52. }
  53. }

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

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


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

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

store.subscribe()方法总结:

  • 入参函数放入监听队列

  • 返回取消订阅函数

  


  

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

  1. try {
  2. isDispatching = true
  3. currentState = currentReducer(currentState, action)
  4. } finally {
  5. isDispatching = false
  6. }
  7.  
  8. const listeners = currentListeners = nextListeners
  9. for (let i = ; i < listeners.length; i++) {
  10. const listener = listeners[i]
  11. listener()
  12. }

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

所以,这里对dispatch方法做一个总结:

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

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

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

bindActionCreators.js

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

  1. function bindActionCreator(actionCreator, dispatch) {
  2. return (...args) => dispatch(actionCreator(...args))
  3. }
  4.  
  5. export default function bindActionCreators(actionCreators, dispatch) {
  6. if (typeof actionCreators === 'function') {
  7. return bindActionCreator(actionCreators, dispatch)
  8. }
  9.  
  10. if (typeof actionCreators !== 'object' || actionCreators === null) {
  11. throw new Error(
  12. `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
  13. `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
  14. )
  15. }
  16.  
  17. const keys = Object.keys(actionCreators)
  18. const boundActionCreators = {}
  19. for (let i = ; i < keys.length; i++) {
  20. const key = keys[i]
  21. const actionCreator = actionCreators[key]
  22. if (typeof actionCreator === 'function') {
  23. boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
  24. }
  25. }
  26. return boundActionCreators
  27. }

实际情况用到的并不多,惟一的应用场景是当你需要把action creator往下传到一个组件上,却不想让这个组件觉察到Redux的存在,而且不希望把Redux Store或dispatch传给它。


CombineReducers.js

  这个文件中暴露的方法是我们常用的,因为在写reducer的时候,往往需要根据类别不同,写多个reducer,但是根据createStore可以知道,只有一个reducer可以被传入,所以这里的combineReducers就是为了将多个reducer合并成一个reducer的。具体源码如下(经过精简之后,就只剩下30多行了):

  1. // 接受一个对象作为参数,这个对象的值是需要被combine的不同的reducer函数。
  2. // 返回一个函数, 这个函数就是一个大的reducer了。
  3. export default function combineReducers(reducers) {
  4. // 获取reducer的所有的keys数组。
  5. const reducerKeys = Object.keys(reducers)
  6. // 最终的reducer对象。
  7. const finalReducers = {}
  8. for (let i = ; i < reducerKeys.length; i++) {
  9. const key = reducerKeys[i]
  10. // 将所有的reducer重新放在finalReducers中,相当于浅拷贝。
  11. if (typeof reducers[key] === 'function') {
  12. finalReducers[key] = reducers[key]
  13. }
  14. }
  15. // 获取最终的所有的reducers数组。
  16. const finalReducerKeys = Object.keys(finalReducers)
  17.  
  18. // 返回了一个函数,可以看出这个函数和我们一个一个定义的reducer函数是类似的,所以,这就是一个大的reducer函数。
  19. return function combination(state = {}, action) {
  20. let hasChanged = false
  21. const nextState = {}
  22. for (let i = ; i < finalReducerKeys.length; i++) {
  23. const key = finalReducerKeys[i]
  24. const reducer = finalReducers[key]
  25. const previousStateForKey = state[key]
  26. const nextStateForKey = reducer(previousStateForKey, action)
  27. nextState[key] = nextStateForKey
  28. hasChanged = hasChanged || nextStateForKey !== previousStateForKey
  29. }
  30. return hasChanged ? nextState : state
  31. }
  32. }

所以这个combineReducers还是很明确的,就是将所有的reducer组合成一个大的。


compose.js

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

  1. export default function compose(...funcs) {
  2. if (funcs.length === ) {
  3. return arg => arg
  4. }
  5.  
  6. if (funcs.length === ) {
  7. return funcs[]
  8. }
  9.  
  10. return funcs.reduce((a, b) => (...args) => a(b(...args)))
  11. }

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


applyMiddleware.js

  这个函数用于 store 增强

  1. export default function applyMiddleware(...middlewares) {
  2. return (createStore) => (reducer, preloadedState, enhancer) => {
  3. const store = createStore(reducer, preloadedState, enhancer)
  4. let dispatch = store.dispatch
  5. let chain = []
  6.  
  7. const middlewareAPI = {
  8. getState: store.getState,
  9. dispatch: (action) => dispatch(action)
  10. }
  11. chain = middlewares.map(middleware => middleware(middlewareAPI))
  12. dispatch = compose(...chain)(store.dispatch)
  13.  
  14. return {
  15. ...store,
  16. dispatch
  17. }
  18. }
  19. }

  用法大致如下:

  1. const store = createStore(reducer,applyMiddleware(…middlewares))
  2. or
  3. const store = createStore(reducer,{},applyMiddleware(…middlewares))

  

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

  1. function createThunkMiddleware(extraArgument) {
  2. return function (_ref) {
  3. var dispatch = _ref.dispatch,
  4. getState = _ref.getState;
  5. return function (next) {
  6. return function (action) {
  7. if (typeof action === 'function') {
  8. return action(dispatch, getState, extraArgument);
  9. }
  10.  
  11. return next(action);
  12. };
  13. };
  14. };
  15. }

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


一般,我们认为redux属于函数式编程,即函数是第一等公民、数据是不可变的(在reducer中,我们希望每次返回一个新的state,而不是修改旧的state,然后返回,所以这里强调的就是不可变的)、有确定的输入就有确定的输出。   整体来说,可能redux不是纯纯的函数式编程,但是也比较符合函数式编程的风格了。

如下:

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

如下:

  1. const me = {name: 'Morgan'};
  2.  
  3. me.skill = 'React'; //这样不好,拿不准me里是啥了
  4. const newMe = {...me, skill: 'React'}; //这样,me不会被修改

  

redux源码解读的更多相关文章

  1. redux源码解读(二)

    之前,已经写过一篇redux源码解读(一),主要分析了 redux 的核心思想,并用100多行代码实现一个简单的 redux .但是,那个实现还不具备合并 reducer 和添加 middleware ...

  2. redux源码解读(一)

    redux 的源码虽然代码量并不多(除去注释大概300行吧).但是,因为函数式编程的思想在里面体现得淋漓尽致,理解起来并不太容易,所以准备使用三篇文章来分析. 第一篇,主要研究 redux 的核心思想 ...

  3. Redux 源码解读 —— 从源码开始学 Redux

    已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...

  4. 手把手教你撸一套Redux(Redux源码解读)

    Redux 版本:3.7.2 Redux 是 JavaScript 状态容器,提供可预测化的状态管理. 说白了Redux就是一个数据存储工具,所以数据基础模型有get方法,set方法以及数据改变后通知 ...

  5. Redux 源码解读--createStore,js

    一.依赖:$$observable.ActionTypes.isPlainObject 二.接下来看到直接 export default 一个 createStore 函数,下面根据代码以及注释来分析 ...

  6. 技本功丨知否知否,Redux源码竟如此意味深长(上集)

    夫 子 说 元月二号欠下袋鼠云技术公号一篇关于Redux源码解读的文章,转眼月底,期间常被“债主”上门催债.由于年底项目工期比较紧,于是债务就这样被利滚利.但是好在这段时间有点闲暇,于是赶紧把这篇文章 ...

  7. 通过ES6写法去对Redux部分源码解读

    在Redux源码中主要有四个文件createStore,applyMiddleware,bindActionCreators,combineRedures createStore.js export ...

  8. Redux源码分析之applyMiddleware

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  9. Redux源码分析之bindActionCreators

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

随机推荐

  1. MySQL系列(一)--基础知识大总结

    MySQL系列(一)---基础知识大总结 前言:本文主要为mysql基础知识的大总结,mysql的基础知识很多,这里只是作为简单的介绍,但是具体的细节还是需要自行搜索.当然本文还有很多遗漏的地方,后续 ...

  2. Selenium模拟JQuery滑动解锁

    滑动解锁一直做UI自动化的难点之一,我补一篇滑动解锁的例子,希望能给初做Web UI自动化测试的同学一些思路. 首先先看个例子. https://www.helloweba.com/demo/2017 ...

  3. jmeter之beanshell提取json数据

    Jmeter BeanShell PostProcessor提取json数据 假设现有需求: 提取sample返回json数据中所有name字段对应的值,返回的json格式如下: {“body”:{“ ...

  4. JavaScript 学习推荐

    主要是个人的学习网站,书籍推荐,还有个人学习经历,以及一些学习经验或技巧 JavaScript学习网站推荐 如果想快速入门,这些是很推荐的网站      快速入门,很快能让你了解前端,有什么,做什么, ...

  5. RobotFramwork安装报错name 'execfile' is not defined

    安装RobotFramwork的时候,提示了这个?是什么原因呢? 本机装的是python3.6: 经官方回复得知识因为python的版本不兼容该模块的安装. 官方认定版本是2.7,所以这里推荐大家玩p ...

  6. log4j(一)——为什么要用log4j?

    一:试验环境 OS:win7 JDK:jdk7 Log4j:1.2.17(好尴尬,原本是想试验下log4j2的,结果阴差阳错用了这个版本,不过幸好,试验也不白试验,试验的作用是一样的) 二:先看两个简 ...

  7. iOS imageio nsurlsession 渐进式图片下载

    一.图片常用加载格式分两种 一般线性式 和交错/渐进式 自上而下线性式 先模糊再清晰 就概率上而言线性式使用最多,应为他所占空间普片比渐进式小.而这两种方式对于app端开发人员无需关心,这种图片存储格 ...

  8. css3-d ,动画,圆角

    一.3D 开启元素3D transform-style: preserve-3d; Z轴 正数 屏幕外,反之屏幕内 近大远小 perspective: length (必须大于等于0) -- 在3D元 ...

  9. tomcat 与 java web中url路径的配置以及使用规则详情(长期更新)

    首先我们看一下在myeclipse中建立的java web项目的结构 在这里我们需要注意这个webroot也就是我们在tomcat里的webapp里面的应用 之所以每一个项目都有这个webroot,是 ...

  10. 【EntityFramework 6.1.3】个人理解与问题记录(2)

    前言 才看完一季动漫,完结撒花,末将于禁,原为曹家世代赴汤蹈火!想必看过的都会知道这个,等一下要不吐槽一下翻拍的真人版,○( ^皿^)っHiahia-,好了快醒醒改办正事儿了,好的,我们接着上一篇文章 ...