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

  • 第一篇,主要研究 redux 的核心思想和实现,并用100多行的代码实现了其核心功能,相信看完之后,你会完全理解 redux的核心。这里甩掉 combindReducersapplyMiddleware,不会涉及很高深的柯里化、高阶、归并的思想,但是需要你对闭包有一定的理解。其实,redux 源码本身并不可怕,可怕的是网上太多文章把他和函数式放在一起来分析(装逼)了!!!吓得我们一看到就想跑了。
  • 第二篇, 理解了 redux 的核心之后, 我们会分析 reducers 合并(即 combindReducers)的实现。
  • 第三篇会分析增强器(即 applyMiddleware)的实现,这是最体现函数式风格的地方,并实现一个处理异步请求的 promise 中间件。

在解读 redux 源码之前,我们首先要弄清楚一个问题,就是 reduxreact-redux 不是同一个东东。 react-redux 是为 react 而定制的,主要是提供 Provider 组件和 connect 方法,方便于我们把 reduxreact组件 绑定起来。但是, redux 是没有限制说一定要跟 react 一起使用的。本文只介绍 redux ,不涉及 react 或者 react-redux 。因为我觉得,如果把 reduxreact 放在一起讨论,反而会加深了理解的复杂度,分散了我们的注意力,从而影响我们分析源码进度。现在要分析 redux 源码,那就只专注于 redux,甩开 react , 就连后面的测试例子,也不要引入 react ,就简单的使用原生html和js测试一下就OK了。

什么是 redux 呢?, 这里也不展开介绍了。就简单的回顾一下 redux 的具体用法:

  1. 定义一个 reducer 函数
  2. 调用 redux.createStore(reducer) 方法创建 store 实例
  3. 通过store.subscribe(callback) 方法订阅回调事件(即状态变化时会触发回调函数callback)
  4. 通过用户交互(如点击事件)调用 store.dispatch(action), 改变 store 的状态

可能用些朋友会说,我从来没有用过 store.subscribe 啊,那是因为你使用了 react-redux, 在 connet() 的时候帮你做了这一步。好吧,说好了不扯 react的。那下面我们就就一步步的来实现 redux 的核心功能吧。

首先来看一下 createStore, 我们平时的用法如下:

const store = createStore(reducer, preloadedState, enhance)

可以接受3个参数,第一个是自定义的reducer函数, 第二个是初始状态,第三个是增强器(即 applyMiddleware()返回的东西),因为前面已经说过了,这里我们不会涉及到 applyMiddleware,所以,我们的 createStore 只接收2个参数,如下:

function createStore(reducer, preloadedState) {
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
} // 定义一些变量,后面几乎所有的方法都会用到,这就是闭包的力量!
let currentState = preloadedState // state
let listeners = [] // 订阅事件列表
let isDispatching = false // 是否正在执行reducer
}

createStore 参数和可能会用到的变量定义好了,我们需要实现三个函数,分别是 store.getStatestore.subscribestore.dispatch

首先来实现 store.getState 方法,这个方法没有好说的,就是把闭包里面的 currentState 返回出去就行了,代码如下:

function createStore(reducer, preloadedState) {
// 省略和上面重复的代码 // 获取state
function getState() {
// 如果正在执行reducer,则抛出异常
if (isDispatching) {
throw new Error('You may not call store.getState() while the reducer is executing. ')
}
return currentState;
}
}

接着我们来实现 store.subscribe。这个方法是用来添加订阅回调函数的。首先要判断传进来的参数是不是函数类型,然后,把他它push到回调队列(数组)里面。因为可能后面需要把这个回调取消掉,所以还要返回一个方法给外部调用取消,实现代码如下:

function createStore(reducer, preloadedState) {
// 省略和上面重复的代码 // 添加订阅事件
function subscribe(listener) {
if(typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
} let isSubscribed = true;
listeners.push(listener); // 返回一个取消订阅事件的函数
return function unsubscribe() {
if(!isSubscribed) {
return;
} if(isDispatching) {
throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ');
} isSubscribed = false; const index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
}
}

最后,我们再来看一下 store.dispatch 方法的实现。 dispatch 接受的参数类型是一个 action 。我们来回顾一下 action是什么鬼?他要求是一个原生对象,而且必须要有 type 属性,还有可能有 payload 属性。如下是我们的一个用法 :

store.dispatch({
type: 'ADD_SHOPPING',
payload: 1
})

调用store.dispatch(action), 它的返回值也是 action。下面代码是 store.dispatch()的实现:

function createStore(reducer, preloadedState) {
// 省略和上面重复的代码 function dispatch(action) {
// 如果action不是原生对象,则抛出异常
// 因为我们期待的action结构为"{type: 'xxx', payload: 'xxx'}"的原生对象
if(Object.prototype.toString.call(action, null) !== '[object Object]') {
throw new Error('Actions must be plain objects. ');
} if(typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ')
} if(isDispatching) {
throw new Error('Reducers may not dispatch actions.')
} // 开始调用reducer获取新状态。因为可能会出错需要用try-catch
// 并且不管成功失败,执行完毕后都要设置isDispatching=true
try {
isDispatching = true;
currentState = reducer(currentState, action);
} finally {
isDispatching = false;
} // 遍历所有通过store.subscribe()绑定的的订阅事件,并调用他们
listeners.forEach((listener) => {
listener();
}) return action;
}
}

关于 redux 的分析就写到这里的了。下面是前面分析的代码整合到了一起。

function createStore(reducer, preloadedState) {
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
} let currentState = preloadedState // state
let listeners = [] // 订阅事件列表
let isDispatching = false // 是否正在执行reducer function getState() {
// 如果正在执行reducer,则抛出异常
if (isDispatching) {
throw new Error('You may not call store.getState() while the reducer is executing. ')
}
return currentState;
} // 添加订阅事件
function subscribe(listener) {
if(typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
} let isSubscribed = true;
listeners.push(listener); // 返回一个取消订阅事件的函数
return function unsubscribe() {
if(!isSubscribed) {
return;
} if(isDispatching) {
throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ');
} isSubscribed = false; const index = listeners.indexOf(listener);
listeners.splice(index, 1);
}
} function dispatch(action) {
// 如果action不是原生对象,则抛出异常
// 因为我们期待的action结构为"{type: 'xxx', payload: 'xxx'}"的原生对象
if(!isPlainObject(action)) {
throw new Error('Actions must be plain objects. ');
} if(typeof action.type === 'undefined') {
throw new Error('Actions may not have an undefined "type" property. ')
} if(isDispatching) {
throw new Error('Reducers may not dispatch actions.')
} // 开始调用reducer获取新状态。因为可能会出错需要用try-catch
// 并且不管成功失败,执行完毕后都要设置isDispatching=true
try {
isDispatching = true;
currentState = reducer(currentState, action);
} finally {
isDispatching = false;
} // 遍历所有通过store.subscribe()绑定的的订阅事件,并调用他们
listeners.forEach((listener) => {
listener();
}) return action;
} // 将getState, subscribe, dispatch这三个方法暴露出去
// 创建了store实例之后,可以store.getState()、store.subscripbe()...
return {
getState,
subscribe,
dispatch
}
}

完整的代码和测试例子,可以到我的github下载 点击进入simplest-redux 。如果觉得我分析得还不是太清楚的,建议把github上的代码clone下来,自己多看几遍,并在demo中运行调试几下就会明白的了。

最新文章

  1. Oracle死锁
  2. SQL表连接查询(inner join、full join、left join、right join)
  3. C#Light for Unity 新例子
  4. 1、CC2541蓝牙4.0芯片中级教程——基于OSAL操作系统的运行流程了解+定时器和串口例程了解
  5. SecureCRT自动备份脚本-华为
  6. 第四节:监视AppDomain
  7. 几乎每个文件里面都有 #ifdef __cplusplus extern "C" { #endif 可我没找到程序里那个地方定义了__cplusplus 啊?这又是怎么回事呢?
  8. 【vc】5_文本编程
  9. poj 3177
  10. PHP中Global和Local范围以及Static变量
  11. JQuery EasyUI的常用组件
  12. C++编写ATM
  13. SharePoint 查找字段内部名称的小方法
  14. vue项目安装vux
  15. 微信小程序自动化测试--接口测试
  16. 运维案例 | Exchange2010数据库损坏的紧急修复思路
  17. How to find SPRO path by t-code name
  18. [Vue warn]: Attribute "id" is ignored on component <div> because the component is a fragment instanc
  19. es中的停用词
  20. OC - 缓存 - NSCache - 介绍

热门文章

  1. SpringMVC云题库错题及答案汇总-2
  2. Python 12306登陆详细分析及操作
  3. 页面制作学习笔记:D1.概述
  4. 2018-2019-2 20175224 实验一《Java开发环境的熟悉》实验报告
  5. cocso引擎整体流程
  6. 左侧 随着页面滚动固定 fixed. scroll .scrollTop
  7. Tensorflow系列——Saver的用法
  8. tomcat免安装版做成windows系统服务
  9. linux 保存git的账号密码
  10. Spark SQL / Catalyst 内部原理 与 RBO