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 https://github.com/zhanyuzhang/simplest-redux . 如果觉得我分析得还不是太清楚的, 建议把 github 上的代码 clone 下来, 自己多看几遍, 并在 demo 中运行调试几下就会明白的了.
来源: https://www.cnblogs.com/yugege/p/9408624.html