redux源码分析
前几天和小伙伴讨论一个问题,就是在reducer方法里面拿到state后,如果直接对state对象做修改,但是还没有return出来,到这一步的时候会不会已经修改了store里的数据,我当时觉得应该不会,但是实在也想不明白,如果不做深拷贝,那还有其他办法能避免修改原始数据吗?但是redux中又不可能对数据进行深拷贝,这样做的代价太大了,于是带着一点好奇心,研究了一下redux源码。
createStore
redux源码还是比想象中的要精炼很多的,除去注释和打印错误信息,核心代码大概在两三百行左右,我们就先从createStore这个入口函数一探究竟。
从返回参数来看,createStore函数主要返回了这四个我们常用的函数。
进入到create函数里面看,第一个判断应该是当用户没有给初始化的数据,直接将enhancer函数(一般是applyMiddleware)当第二个参数传进来的时候做的一些默认处理,如果enhancer是一个函数,那么就会把reducer和preloadedState传给enhancer,我们知道enhancer一般是一些中间件函数,这里的reducer一般是combineReducers这个函数的返回值,我们再来看看combineReducers和applyMiddleware这两个函数。
combineReducers
删除掉多余的注释和打印信息后,完整的combineReducers函数是这样的:
参数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函数:
applyMiddleware函数就更加简练了一些,我们一般会把redux-thunk、redux-logger这种中间件当做参数传给applyMiddleware。
redux的中间件是在action触发后执行的,所以中间件内部必须拿到完整的state、dispatch和action,这里使用compose包裹了中间件方法,最终返回了一个新的dispatch,可以理解为这个dispatch是经过中间件加强后的dispatch。
compose
这里是compose的源码,我们可以明显看出来这里是将dispatch再次作为参数放进去的,最后得到一个强化的dispatch,结合redux-logger来理解,大概是传入dispatch后对其加了打印的功能,之后再返回出来。
dispatch
再回头来看我们的createStore函数,我们关键来看一下对应的几个函数:
首先是我们的dispatch函数,dispatch函数主要做了两件事,一个是执行reducer函数拿到最新的state,另一个是执行subscribe的事件。
dispatch接收了一个action当参数,通过isDispatching来判断是否执行reducer,这也就不可能出现多个dispatch同时执行的情况了,因为这样会干扰store的值。这里看到会把currentState传到reducer里面,更新后得到了新的currentState,之后还执行了一下listener函数,这个函数是从nextListeners里面拿到的。
subscribe
subscribe会传入一个回调函数,这个函数一般是监听redux中状态变化后执行的,nextListeners里面保存着所有需要执行的回调,如果subscribe函数执行两次,那就是卸载当前加载上的listener。
这样的话,其实还是有一个问题,如果我们用subscribe监听了ReactDOM.render,这样我们每次发送dispatch,即使最后state没有变化,页面也是会重新render。
重写
这里是自己重写的简练版redux:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | /// 这里需要对参数为0或1的情况进行判断 const compose = (...funcs) => { if (!funcs) { return args => args } if (funcs.length === 1) { return funcs[0] } return funcs.reduce((f1, f2) => (...args) => f1(f2(...args))) } const bindActionCreator = (action, dispatch) => { return (...args) => dispatch(action(...args)) } const createStore = (reducer, initState, enhancer) => { if (!enhancer && typeof initState === "function") { enhancer = initState initState = null } if (enhancer && typeof enhancer === "function") { return enhancer(createStore)(reducer, initState) } let store = initState, listeners = [], isDispatch = false; const getState = () => store const dispatch = (action) => { if (isDispatch) return action // dispatch必须一个个来 isDispatch = true store = reducer(store, action) isDispatch = false listeners.forEach(listener => listener()) return action } const subscribe = (listener) => { if (typeof listener === "function") { listeners.push(listener) } return () => unsubscribe(listener) } const unsubscribe = (listener) => { const index = listeners.indexOf(listener) listeners.splice(index, 1) } return { getState, dispatch, subscribe, unsubscribe } } const applyMiddleware = (...middlewares) => { return (createStore) => (reducer, initState, enhancer) => { const store = createStore(reducer, initState, enhancer) let chain = middlewares.map(middleware => middleware(store)) store.dispatch = compose(...chain)(store.dispatch) return { ...store } } } const combineReducers = reducers => { const finalReducers = {}, nativeKeys = Object.keys nativeKeys(reducers).forEach(reducerKey => { if(typeof reducers[reducerKey] === "function") { finalReducers[reducerKey] = reducers[reducerKey] } }) return (state, action) => { const store = {} nativeKeys(finalReducers).forEach(key => { const reducer = finalReducers[key] const nextState = reducer(state[key], action) store[key] = nextState }) return store } } |
总结
redux源码实现很精简,比想象中的还要简单,react-redux在redux基础中多了Provider和connect两个方法,通过context将store传给Provider包裹的组件,之后会再开一篇文章分析react-redux的源码。
原文http://ygy.online/2018/07/25/redux%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/
相关文章
- 比特币源码分析--C++11和boost库的应用
- 微信仿真平台的设计和实现(设计+源码)_kaic
- 企业数据采集与分析(论文+源码)_kaic
- 基于JAVA技术的校园论坛系统的设计与实现(论文+源码)_kaic
- MyBatis 学习笔记(四)---源码分析篇---配置文件的解析过程(一)
- 源码分析
- 爬虫篇-物联网平台【附源码】
- 学生成绩管理分析系统的设计与实现(论文+源码)_kaic
- java中的锁之AbstractQueuedSynchronizer源码分析(一)
- 从源码分析 MGR 的流控机制
- Tomcat源码分析 (一)----- 手写一个web服务器
- Java HashMap实例源码分析
- 【Redisson】三.可重入锁-watchdog维持加锁源码
- 【Feign】Feign源码分析(二): FeignClient实例化的过程
- Java并发之AQS源码分析(一)
- VS 查看引用的DLL/Nuget包源码时,无法看到注释
- Facebook Rebound 弹性动画库 源码分析
- Unity3d C# 实现AA包(Addressables)资源热更新的多个包异步加载并显示加载实时进度功能(含源码)
- Unity3d C# 实现两点的画线和测距效果功能(含源码)
- 【Zookeeper】源码分析之Watcher机制(一)
- 【JUC】JDK1.8源码分析之ConcurrentLinkedQueue(五)
- 框架源码系列九:依赖注入DI、三种Bean配置方式的注册和实例化过程
- werkzeug源码分析——从官网的示例代码开始
- Kibana6.x.x源码分析--启动时无反应分析
- muduo源码分析:组成结构
- 【Android 事件分发】事件分发源码分析 ( ViewGroup 事件传递机制 三 )
- live555源码分析----RSTPServer创建过程分析
- Flutter Bloc源码分析
- Function score查询的应用及源码解析
- Solr初始化源码分析-Solr初始化与启动
- Redux源码分析之基本概念
- 【Spring Cloud Alibaba】(三)OpenFeign扩展点实战 + 源码详解
- HBase源码分析之HRegionServer上MemStore的flush处理流程(二)