[关闭]
@gyyin 2020-02-06T15:04:36.000000Z 字数 4131 阅读 297

redux源码分析

React


前几天和小伙伴讨论一个问题,就是在reducer方法里面拿到state后,如果直接对state对象做修改,但是还没有return出来,到这一步的时候会不会已经修改了store里的数据,我当时觉得应该不会,但是实在也想不明白,如果不做深拷贝,那还有其他办法能避免修改原始数据吗?但是redux中又不可能对数据进行深拷贝,这样做的代价太大了,于是带着一点好奇心,加上今天重感冒卧病在床,就抽出时间看了看redux源码。

createStore

redux源码还是比想象中的要精炼很多的,除去注释和打印错误信息,核心代码大概在两三百行左右,我们就先从createStore这个入口函数一探究竟。

redux入口.png-41.8kB

从返回参数来看,createStore函数主要返回了这四个我们常用的函数。
createStore.png-251.1kB

进入到create函数里面看,第一个判断应该是当用户没有给初始化的数据,直接将enhancer函数(一般是applyMiddleware)当第二个参数传进来的时候做的一些默认处理,如果enhancer是一个函数,那么就会把reducer和preloadedState传给enhancer,我们知道enhancer一般是一些中间件函数,这里的reducer一般是combineReducers这个函数的返回值,我们再来看看combineReducers和applyMiddleware这两个函数。

combineReducers

删除掉多余的注释和打印信息后,完整的combineReducers函数是这样的:
combineReducers.png-309.2kB

参数reducers就是我们最后传给combineReducers的那个对象,一般是我们reducer函数的对象集合。
这里代码也很清晰了,将reducer函数以键值对的形式赋给finalReducers对象,并且返回一个combination函数,这个函数可以拿到当前的状态和要执行的action,这两个参数肯定是在createStore里面调用的时候传入的,我们先不用管。
这里使用for循环来遍历每一个reducer函数,这也就意味着,我们每次触发一个action,redux都会遍历并执行一遍所有的reducer函数,直到找到匹配的那个action.type(这里我不得不说react-imvc应该是做了一些优化的,它以action.type作为reducer函数名,这样就不需要去遍历查找了,可以减少很多不必要的工作量)。
并且将执行后的结果nextStateForKey和前一个状态做比较,最后根据判断是return新值还是老值,这也是为什么在reducer函数里面最后一定要return出来一个新的对象or数组才会刷新store,而不能简单的修改一下当前的state,并将其直接返回,因为这里比较的是引用。

applyMiddleware

Middleware这个概念是Redux从其他框架借鉴过来的,本意如下:

middleware是指可以被嵌入在框架接收请求到产生响应过程之中的代码。例如,Express 或者 Koa 的 middleware
可以完成添加 CORS headers、记录日志、内容压缩等工作。

而在Redux中:

middleware被用于解决不同的问题,但其中的概念是类似的。它提供的是位于 action 被发起之后,到达 reducer
之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

看完了combineReducers函数,我们继续分析applyMiddleware函数:

applyMi.png-172kB

applyMiddleware函数就更加简练了一些,我们一般会把redux-thunk、redux-logger这种中间件当做参数传给applyMiddleware。
redux的中间件是在action触发后执行的,所以中间件内部必须拿到完整的state、dispatch和action,这里使用compose包裹了中间件方法,最终返回了一个新的dispatch,可以理解为这个dispatch是经过中间件加强后的dispatch。
我之前一直在想,为什么重新执行了一遍createStore,不能直接将getState和dispatch方法传过来吗?但是又想了想,getState和dispatch形成了闭包,这样会导致createStore中的变量一直无法释放,这样倒不如重新创建一个store。

compose

compose.png-80.9kB

这里是compose的源码,我们可以明显看出来这里是将dispatch再次作为参数放进去的,最后得到一个强化的dispatch,结合redux-logger来理解,大概是传入dispatch后对其加了打印的功能,之后再返回出来。

dispatch

再回头来看我们的createStore函数,我们关键来看一下对应的几个函数:
dispatch.png-152.4kB
首先是我们的dispatch函数,dispatch函数主要做了两件事,一个是执行reducer函数拿到最新的state,另一个是执行subscribe的事件。
dispatch接收了一个action当参数,通过isDispatching来判断是否执行reducer,这也就不可能出现多个dispatch同时执行的情况了,因为这样会干扰store的值。这里看到会把currentState传到reducer里面,更新后得到了新的currentState,之后还执行了一下listener函数,这个函数是从nextListeners里面拿到的。

subscribe

这里我们看一下subscribe函数:
subscribe.png-128kB

subscribe会传入一个回调函数,这个函数一般是监听redux中状态变化后执行的,nextListeners里面保存着所有需要执行的回调,如果subscribe函数执行两次,那就是卸载当前加载上的listener。
这样的话,其实还是有一个问题,如果我们用subscribe监听了ReactDOM.render,这样我们每次发送dispatch,即使最后state没有变化,页面也是会重新render。

重写

这里是自己重写的简练版redux:

  1. /// 这里需要对参数为0或1的情况进行判断
  2. const compose = (...funcs) => {
  3. if (!funcs) {
  4. return args => args
  5. }
  6. if (funcs.length === 1) {
  7. return funcs[0]
  8. }
  9. return funcs.reduce((f1, f2) => (...args) => f1(f2(...args)))
  10. }
  11. const bindActionCreator = (action, dispatch) => {
  12. return (...args) => dispatch(action(...args))
  13. }
  14. const createStore = (reducer, initState, enhancer) => {
  15. if (!enhancer && typeof initState === "function") {
  16. enhancer = initState
  17. initState = null
  18. }
  19. if (enhancer && typeof enhancer === "function") {
  20. return enhancer(createStore)(reducer, initState)
  21. }
  22. let store = initState,
  23. listeners = [],
  24. isDispatch = false;
  25. const getState = () => store
  26. const dispatch = (action) => {
  27. if (isDispatch) return action
  28. // dispatch必须一个个来
  29. isDispatch = true
  30. store = reducer(store, action)
  31. isDispatch = false
  32. listeners.forEach(listener => listener())
  33. return action
  34. }
  35. const subscribe = (listener) => {
  36. if (typeof listener === "function") {
  37. listeners.push(listener)
  38. }
  39. return () => unsubscribe(listener)
  40. }
  41. const unsubscribe = (listener) => {
  42. const index = listeners.indexOf(listener)
  43. listeners.splice(index, 1)
  44. }
  45. return {
  46. getState,
  47. dispatch,
  48. subscribe,
  49. unsubscribe
  50. }
  51. }
  52. const applyMiddleware = (...middlewares) => {
  53. return (createStore) => (reducer, initState, enhancer) => {
  54. const store = createStore(reducer, initState, enhancer)
  55. let chain = middlewares.map(middleware => middleware(store))
  56. store.dispatch = compose(...chain)(store.dispatch)
  57. return {
  58. ...store
  59. }
  60. }
  61. }
  62. const combineReducers = reducers => {
  63. const finalReducers = {},
  64. nativeKeys = Object.keys
  65. nativeKeys(reducers).forEach(reducerKey => {
  66. if(typeof reducers[reducerKey] === "function") {
  67. finalReducers[reducerKey] = reducers[reducerKey]
  68. }
  69. })
  70. return (state, action) => {
  71. const store = {}
  72. nativeKeys(finalReducers).forEach(key => {
  73. const reducer = finalReducers[key]
  74. const nextState = reducer(state[key], action)
  75. store[key] = nextState
  76. })
  77. return store
  78. }
  79. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注