redux源码浅析(1)

前言

在阅读源码的过程中我也翻阅了大量的资料。一般是先去看别人的源码分析完完整镇过的读一遍之后,看完了之后自己再把官网的clone下来自己再慢慢的阅读添加注释。希望大家在阅读完有感想之后也把官网源码clone下来不照着任何的资料边写注释边阅读完全部源码,这样子更有利于深刻理解。

在工作中其实会经常使用到 redux 数据状态处理库,在一开始的使用中就一直听到说 reducer 必须是一个纯函数,不能有副作用!state 需要有一个默认值等等的约束。当然也踩过含有副作用修改了 state 造成视图层不更新的 bug(state 嵌套过深的锅。。。) 。一直停留在知其然不知其所以然的层次。想到彻底的掌握以及明白 redux 的原理的办法当然就是直接阅读源码啦。而且 redux 非常简洁才 2kb 而已= = 非常值得一读()

为了好方便理解 我会先从 redux 提供的 api 入手 一步一步的深入解读

redux 的基本使用

 1 //常用的三个api
 2 import { createStore, combineReducers, applyMiddleware } from "redux";
 3 //中间件
 4 import thunk from "redux-thunk";
 5 import logger from "redux-logger";
 6 import { userReducer } from "./user/reducer";
 7 import { todoReducer } from "./todo/reducer";
 8 //combineReducers用于合并多个子reducer生成一个总的reducer
 9 const reducers = combineReducers({
10   userStore: userReducer,
11   todoStore: todoReducer
12 });
13 //applyMiddleware使用中间件增强dispatch
14 const enhancer = applyMiddleware(thunk, logger);
15 //createStore 创建一个 Redux store 来以存放应用中所有的 state,应用中应有且仅有一个 store(react-redux有兼容多个store的写法,后面解读react-redux再说啦)
16 const store = createStore(reducers, null, enhancer);
17 //返回的store其实是一个对象,上面提供了一些常用的方法
18 // store.getState()用于获取store的state对象
19 console.log(store.getState());
20 // store.subscribe接受一个函数用于dispatch时执行
21 // 注意 subscribe() 返回一个函数用来注销监听器
22 const unsubscribe = store.subscribe(() => console.log(store.getState()));
23 // 发起一系列 action
24 store.dispatch(addTodo("Learn about actions"));
25 // 停止监听 state 更新
26 unsubscribe();
27 export default store;

 

明白了 redux 的基本用法之后我们就可以深入源码啦

上源码,为了减小篇幅,我删减了很多没用的代码(包括一些错误边界处理,其实很多错误边界处理都很有意思),如果想看完整的redux代码注释的话可以点击这里

createStore

首先要明白的就是 redux 本质就是一个具有增强功能(中间件)的发布订阅函数 subscribe 就是订阅 dispatch 就是发布通知订阅者执行相应的回调 createStore 一开始会进行一系列的判断 判断是否传入了中间件(applyMiddleware(...)) 如果有的话直接返回 enhancer(createStore)(reducer, preloadedState) 没有的话继续执行下面的代码 声明了 dispatch subscribe getState replaceReudcer 这几个函数后直接进行返回

 1 export default function createStore(reducer, preloadedState, enhancer) {
 2   //首先createStore接受三个参数第一个是必须的reducer函数,第二个为state默认值(可传) 第三个enhancer为增强的中间件(可传,redux如此牛逼的原因)
 3   if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
 4     //redux允许第二个参数直接传入中间件  判断第二个参数是否为函数 并且第三个参数为undefined(证明用户省略了state默认值,传入了第二个参数是中间件)
 5     enhancer = preloadedState
 6     preloadedState = undefined
 7   }
 8 
 9   if (typeof enhancer !== 'undefined') {
10     //enhancer 必须是一个函数
11     if (typeof enhancer !== 'function') {
12       throw new Error('Expected the enhancer to be a function.')
13     }
14     //如果传入了中间件的话 那么就直接执行这个中间件函数 可查看applyMiddleware函数(为了方便理解我们先不看有中间件传入的createStore方法 跳过这里)
15     return enhancer(createStore)(reducer, preloadedState)
16   }
17     //reducer必须是函数
18   if (typeof reducer !== 'function') {
19     throw new Error('Expected the reducer to be a function.')
20   }
21  ---------------------分隔符 下面都是不传入中间件执行的代码------------------------------------------
22   let currentReducer = reducer
23   let currentState = preloadedState
24   //订阅监听器的函数组
25   let currentListeners = []
26 
27   //拷贝之前的监听器函数组
28   let nextListeners = currentListeners
29   //是否处于dispatch的标识符
30   let isDispatching = false
31 
32   function ensureCanMutateNextListeners() {
33     //同步最新的监听器函数组  下面在订阅的时候会使用到
34     if (nextListeners === currentListeners) {
35       nextListeners = currentListeners.slice()
36     }
37   }
38   ...省略了dispatch subscribe getState replaceReudcer
39   return {
40     dispatch,
41     subscribe,
42     getState,
43     replaceReducer,
44   }
45 }

 

获取 getState

我们知道执行 store.getState()会把当前的存在 store state 对象返回出来 看源码 惊喜吧没错就是这么简单

function getState() {
  //直接了当的返回当前的state
  return currentState;
}

 

派发 dispatch

dispatch 的作用就是传入一个 action 对象 根据传入的 action 对象之后执行相应的 reducer 函数重新计算新的 state 出来 计算完成之后执行 listeners 里面的订阅函数(也就是 subscribe 啦) 其中要注意的就是一些边界处理 比如 action 默认要有一个 type 属性 以及为什么 reducer 内部不能还有 dispatch 的操作 dispatch 触发 reducer reducer 又触发 dispatch ...堆栈溢出啦 大哥

 1 function dispatch(action) {
 2   //action必须是对象
 3   if (!isPlainObject(action)) {
 4     throw new Error(
 5       "Actions must be plain objects. " +
 6         "Use custom middleware for async actions."
 7     );
 8   }
 9   //action必须有一个type属性
10   if (typeof action.type === "undefined") {
11     throw new Error(
12       'Actions may not have an undefined "type" property. ' +
13         "Have you misspelled a constant?"
14     );
15   }
16 
17   //reducer内部不能有dispatch的操作 不然的话会进行循环调用
18   // 相当于 dispatch() - reducer() - dispatch()......
19   if (isDispatching) {
20     throw new Error("Reducers may not dispatch actions.");
21   }
22 
23   try {
24     //isDispatching置为true 说明已经处于dispatch状态了
25     isDispatching = true;
26     //执行reducer 获取state
27     currentState = currentReducer(currentState, action);
28   } finally {
29     //结束后置为false
30     isDispatching = false;
31   }
32   //这里的作用还是用于更新同步监听器
33   const listeners = (currentListeners = nextListeners);
34   //执行listeners里面的订阅函数
35   for (let i = 0; i < listeners.length; i++) {
36     const listener = listeners[i];
37     listener();
38   }
39 
40   return action;
41 }

 

订阅 subscribe

subscribe 就是添加订阅者的一个方法 执行完成之后返回的函数是解绑的方法 其中要注意的一点就是 ensureCanMutateNextListeners 方法的作用 至于为什么要使用两个监听器数组 nextListeners 和 currentListeners 就是为了同步循环中当前监听器数组和真实监听器数组的长度(防止绑定过程冲解绑造成索引值发生变化)

 1 // const unsubscribe = store.subscribe(() =>
 2 //   console.log(store.getState())
 3 // )
 4 //其中 执行后的返回值是注销监听器
 5 function subscribe(listener) {
 6   //再推入新的订阅者前  先更新拷贝的监听器数组nextListeners
 7   ensureCanMutateNextListeners();
 8   nextListeners.push(listener);
 9 
10   return function unsubscribe() {
11     //删除之前再次更新拷贝的监听器数组nextListeners 确保当前的监听器函数是最新的
12     ensureCanMutateNextListeners();
13     /*ensureCanMutateNextListeners函数的作用就是更新同步当前最新的订阅器和当前的订阅器 假的如果就使用currentListeners作为删除和添加的数组
14       * 就是比如我在订阅的函数中进行注销另外的监听器 类似于
15       * const unsubscribe1 = store.subscribe(() =>{})
16       * const unsubscribe2 = store.subscribe(() =>{ unsubscribe1() })
17       * const unsubscribe3 = store.subscribe(() =>{})
18       *  执行dispatch(action) 在循环的过程中
19       *for (let i = 0; i < listeners.length; i++) {
20       *  const listener = listeners[i];
21       *  listener(); 执行到unsubscribe2的过程中 listener的长度发生了变化减少了1 那么就会造成跳过下一个订阅
22       *}
23       * 所以需要拷贝一个真正的监听器函数组 每次进行解绑时  都把当前的监听器函数与最新的监听器函数进行同步
24       * 因为在订阅第二个函数的过程中 我进行了unsubscribe1的解绑操作 那么currentListeners数组的索引值也发生了改变 所以需要一个拷贝来真正同步真正的订阅器数组
25       * */
26     const index = nextListeners.indexOf(listener);
27     nextListeners.splice(index, 1);
28   };
29 }
30 //replaceReducer一般用于开发环境下的热更新
31 function replaceReducer(nextReducer) {
32   if (typeof nextReducer !== "function") {
33     throw new Error("Expected the nextReducer to be a function.");
34   }
35 
36   currentReducer = nextReducer;
37   dispatch({ type: ActionTypes.REPLACE });
38 }

 

总结

总而言之 createStore 就是一个简单的发布订阅函数(applyMiddleware 用于增强这个函数),接下来分析一波合成子 reducer 的 combineReducer 函数

posted @ 2018-09-13 19:29  carrot萝卜  阅读(124)  评论(0编辑  收藏  举报