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中运行调试几下就会明白的了。

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

  1. redux源码解读(二)

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

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

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

  3. redux源码解读

    react在做大型项目的时候,前端的数据一般会越来越复杂,状态的变化难以跟踪.无法预测,而redux可以很好的结合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. Beta冲刺五

    1.团队TSP 团队任务 预估时间 实际时间 完成日期 对数据库的最终完善 120 150 12.2 对学生注册功能的完善--新增触发器 150 140 11.29 对教师注册功能的完善 150 13 ...

  2. 专业的“python爬虫工程师”需要学习哪些知识?

    学到哪种程度 暂且把目标定位初级爬虫工程师,简单列一下吧: (必要部分) 熟悉多线程编程.网络编程.HTTP协议相关 开发过完整爬虫项目(最好有全站爬虫经验,这个下面会说到) 反爬相关,cookie. ...

  3. ;html5斜体字

    font-style:italic; italic|oblique|normal 依次倾斜,越来越邪:

  4. io 的一些简单说明及使用

    io 流简述: i->inputStream(输入流) o->outputStream  (输出流) IO流简单来说就是Input和Output流,IO流主要是用来处理设备之间的数据传输, ...

  5. Mac下安装证书fiddlerRoot.cer

    Step 1: 设置Mac的代理如下 Step 2:打开127.0.0.1:8888,下载fiddlerRoot.cer; Step 3:下载好了,双击安装,但是默认这个证书是不可信的,你需要在钥匙串 ...

  6. 马凯军201771010116《面向对象与程序设计Java》第十七周学习总结

    一.理论知识部分 Java 的线程调度采用优先级策略:优先级高的先执行,优先级低的后执行:多线程系统会自动为每个线程分配一个优先级,缺省时,继承其父类的优先级: 任务紧急的线程,其优先级较高: 同优先 ...

  7. input()和print()函数同时输入输出多个数据--python3

    使用input()和print()函数同时输入输出多个数据,需要空格分割输入信息 #!/usr/bin/python3#-*- conding:utf-8 -*- name, age, QQ = in ...

  8. Java判断字符串是否有重复

      检测是否重复: public static boolean checkDifferent(String iniString) { boolean isbool = false; char[] ch ...

  9. linux解压缩文件名乱码问题 亲测可用

    unar 这个工具会自动检测文件的编码,也可以通过-e来指定:unar file.zip 即可解压出中文文件.

  10. SQLI DUMB SERIES-17

    (1)无论怎么输入username,都没有回显.尝试改变password的输入.找到了闭合方式:单引号 (2)报错注入: 爆库名 admin&passwd=admin' and extract ...