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. SSL backend error when using OpenSSL pycurl install error

    centos7 pip install pycurl 错误 pip uninstall pycurl export PYCURL_SSL_LIBRARY=nss pip install pycurl ...

  2. Codeforces Round #162 (Div. 1) B. Good Sequences (dp+分解素数)

    题目:http://codeforces.com/problemset/problem/264/B 题意:给你一个递增序列,然后找出满足两点要求的最长子序列 第一点是a[i]>a[i-1] 第二 ...

  3. 二叉树:B+tree等

    二叉树:(树是一种可以递归定义数据结构) 度:节点的个数 深度:层数(即从根点到叶子节点的层数) 满二叉树:指底层叶子节点左右均存在的二叉树. 完全二叉树:指底层叶子节点的右侧均存在的二叉树. 一般二 ...

  4. Java的类型强制转换

    不说基本类型,没什么意思. 小括号的类型转换,在引用上,表示我坚定的确信,该未知类型一定是我转的类型,或者是我转的类型的子类. 这个转换逻辑和基本类型是不一致的.它不会进行任何具体的操作,只是一种标识 ...

  5. java实现截图功能

    package Jietu; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Robot; import j ...

  6. 我的学习目标(目前已初步学习完Java语言基础)

    操作系统.尤其是内存/线程/进程方面 计算机网络协议,重点关注 TCP/UDP/HTTP. 数据结构与算法. 数据库 设计模式,熟练掌握常用的几种设计模式. Java语言基础.熟悉java语言基础,了 ...

  7. Linux 驱动——Button驱动7(Timer)消抖

    button_drv.c驱动文件: #include <linux/module.h>#include <linux/kernel.h>#include <linux/f ...

  8. React相关:npm,ES6,

    1.NPM: 参考:npm使用入门  npm 学习笔记整理 2.ES6参考:ES6 let命令:ES6新增了let命令,用来声明变量.它的用法类似于var,但是所声明的变量,只在let命令所在的代码块 ...

  9. Delphi7第三方控件

    控件安装(安装时建议先关闭Delphi) 1.只有一个DCU文件的组件. DCU文件是编译好的单元文件,这样的组件是作者不想把源码公布.一般来说,作者必须说明此组件适合Delphi的哪种版本,如果版本 ...

  10. python智能提示配置

    Package Control 安装方法 1.通过快捷键 ctrl+` 或者 View > Show Console 打开控制台,然后粘贴相应的 Python 安装代码: 2.Sublime T ...