redux源码解读(一)
redux 的源码虽然代码量并不多(除去注释大概300行吧)。但是,因为函数式编程的思想在里面体现得淋漓尽致,理解起来并不太容易,所以准备使用三篇文章来分析。
- 第一篇,主要研究
redux的核心思想和实现,并用100多行的代码实现了其核心功能,相信看完之后,你会完全理解redux的核心。这里甩掉combindReducers和applyMiddleware,不会涉及很高深的柯里化、高阶、归并的思想,但是需要你对闭包有一定的理解。其实,redux源码本身并不可怕,可怕的是网上太多文章把他和函数式放在一起来分析(装逼)了!!!吓得我们一看到就想跑了。 - 第二篇, 理解了
redux的核心之后, 我们会分析reducers合并(即combindReducers)的实现。 - 第三篇会分析增强器(即
applyMiddleware)的实现,这是最体现函数式风格的地方,并实现一个处理异步请求的promise中间件。
在解读 redux 源码之前,我们首先要弄清楚一个问题,就是 redux 和 react-redux 不是同一个东东。 react-redux 是为 react 而定制的,主要是提供 Provider 组件和 connect 方法,方便于我们把 redux 和 react组件 绑定起来。但是, redux 是没有限制说一定要跟 react 一起使用的。本文只介绍 redux ,不涉及 react 或者 react-redux 。因为我觉得,如果把 redux 和 react 放在一起讨论,反而会加深了理解的复杂度,分散了我们的注意力,从而影响我们分析源码进度。现在要分析 redux 源码,那就只专注于 redux,甩开 react , 就连后面的测试例子,也不要引入 react ,就简单的使用原生html和js测试一下就OK了。
什么是 redux 呢?, 这里也不展开介绍了。就简单的回顾一下 redux 的具体用法:
- 定义一个
reducer函数 - 调用
redux.createStore(reducer)方法创建store实例 - 通过
store.subscribe(callback)方法订阅回调事件(即状态变化时会触发回调函数callback) - 通过用户交互(如点击事件)调用
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.getState 、store.subscribe 和 store.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源码解读(一)的更多相关文章
- redux源码解读(二)
之前,已经写过一篇redux源码解读(一),主要分析了 redux 的核心思想,并用100多行代码实现一个简单的 redux .但是,那个实现还不具备合并 reducer 和添加 middleware ...
- Redux 源码解读 —— 从源码开始学 Redux
已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...
- redux源码解读
react在做大型项目的时候,前端的数据一般会越来越复杂,状态的变化难以跟踪.无法预测,而redux可以很好的结合react使用,保证数据的单向流动,可以很好的管理整个项目的状态,但是具体来说,下面是 ...
- 手把手教你撸一套Redux(Redux源码解读)
Redux 版本:3.7.2 Redux 是 JavaScript 状态容器,提供可预测化的状态管理. 说白了Redux就是一个数据存储工具,所以数据基础模型有get方法,set方法以及数据改变后通知 ...
- Redux 源码解读--createStore,js
一.依赖:$$observable.ActionTypes.isPlainObject 二.接下来看到直接 export default 一个 createStore 函数,下面根据代码以及注释来分析 ...
- 技本功丨知否知否,Redux源码竟如此意味深长(上集)
夫 子 说 元月二号欠下袋鼠云技术公号一篇关于Redux源码解读的文章,转眼月底,期间常被“债主”上门催债.由于年底项目工期比较紧,于是债务就这样被利滚利.但是好在这段时间有点闲暇,于是赶紧把这篇文章 ...
- 通过ES6写法去对Redux部分源码解读
在Redux源码中主要有四个文件createStore,applyMiddleware,bindActionCreators,combineRedures createStore.js export ...
- Redux源码分析之applyMiddleware
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- Redux源码分析之bindActionCreators
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
随机推荐
- div上下切换(新增、删除、上下div切换)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title&g ...
- admin 显示多对多字段
class BookAdmin(admin.ModelAdmin): def 作者(self, object): return [a.name for a in object.author.all() ...
- swiper 父级元素display:none 之bug
问题描述: 同一个页面,点击底部tab按钮切换div的显示与隐藏,点击到第四个页面时 轮播图总是不动,出bug function start(){ var mySwiper = new Swiper( ...
- pytest自动化6:pytest.mark.parametrize装饰器--测试用例参数化
前言:pytest.mark.parametrize装饰器可以实现测试用例参数化. parametrizing 1. 下面是一个简单是实例,检查一定的输入和期望输出测试功能的典型例子 2. 标记单 ...
- Spring Boot 整合JDBCTemplate
1. 首先配置pom.xml 1.1 dbcm2 是数据源类型,表示配置dataSource的方式 1.2 spring-boot-starter-jdbc是表示让spring boot 支持jdbc ...
- 【转】IE沙箱拖拽安全策略解析
https://xlab.tencent.com/cn/2015/12/17/ie-sandbox-drop-security-policy/ IE沙箱逃逸是IE浏览器安全研究的一个重要课题,其中有一 ...
- 精进之路之HashMap
HashMap本质的核心就是“数组+链表”,数组对于访问速度很快,而链表的优势在于插入速度快,HashMap集二者于一身. 提到HashMap,我们不得不提各个版本对于HashMap的不同.本文中先从 ...
- java多态——基础
多态 定义: 一个接口,多种实现,就是多种状态 价值: 特点:多态存在的特点,就是必须要有继承.覆盖.父类变量调用子类对象 场景: 首先检查父类中是否有该方法,如果没有,则编译错误:如果有,则 ...
- 学习Git笔记(更新中)
参考网址:https://blog.csdn.net/zmx729618/article/details/52174373 跟着练习一下,写的很好. Git:是一个分布式版本控制系统. GitHub: ...
- ArrayList、LinkedList和vector的区别
1.ArrayList和Vector都是数组存储,插入数据涉及到数组元素移动等操作,所以比较慢,因为有下标,所以查找起来非常的快. LinkedList是双向链表存储,插入时只需要记录本项的前后项,查 ...