回顾一下 redux 的目录结构:

.\REDUX\SRC │ applyMiddleware.js │ bindActionCreators.js │ combineReducers.js │ compose.js │ createStore.js │ index.js │ └─utils actionTypes.js isPlainObject.js warning.js

reduxindex.js 中一共暴露了5个 API, 上一篇文章讲了下和 redux 关联性不太大的 compose 。现在正式讲一讲最核心的 createStore

createStore.js

createStore 大概是长成这个样子的:

import $$observable from 'symbol-observable'import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'export default function createStore(reducer, preloadedState, enhancer) {// 1. 对传入参数的顺序处理// 先忽略这一块// 2. 变量的定义let currentReducer = reducerlet currentState = preloadedStatelet currentListeners = []let nextListeners = currentListenerslet isDispatching = false// 3. 一系列函数定义function ensuerCanMutateNextListeners(){}function getState(){}function subscribe(listener){}function dispatch(action){}function replaceReducer(nextReducer){}function observable(){}// 4. dispatch一个初始化的actiondispatch({ type: ActionTypes.INIT })// 5. 返回store对象return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}
}
复制代码

我们分别对这五块来看看。

1. 参数的顺序处理

这一步就是对传入给 createStore 的三个参数 reducerpreloadedStateenhancer 的顺序调整。

export default function createStore(reducer, preloadedState, enhancer) {if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {// 第二个参数是一个函数,没有第三个参数的情况enhancer = preloadedStatepreloadedState = undefined}if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {// enhancer 不是函数就报错throw new Error('Expected the enhancer to be a function.')}// enhancer就是高阶函数,强化了本身这个createStore的函数,拿到增强后的createStore函数去处理// applyMiddleware这个函数还会涉及到这个return enhancer(createStore)(reducer, preloadedState)}if (typeof reducer !== 'function') {// reducer不是函数报错throw new Error('Expected the reducer to be a function.')}// 其他代码省略
}
复制代码

2. 变量的定义

  let currentReducer = reducerlet currentState = preloadedStatelet currentListeners = []let nextListeners = currentListenerslet isDispatching = false
复制代码
  • currentReducer 当前 storereducer,由 createStore 传入的第一个参数 reducer 初始化
  • currentState 保存当前整个 state 的状态,初始值就是 createStore 传进来的第二个参数 preloadedState,相当于 store 的初始值
  • currentListeners 当前的监听器,默认是空
  • nextListeners 下一个监听器,由 currentListeners 赋值
  • isDispatching 当前的 store 是否正在 dispatch 一个action

全是闭包保存的变量

3. 函数的定义

createStore 的最后,dispatch 了一个 { type: ActionTypes.INIT } 对象,那就按图索骥,从 dispatch 函数开始看。

先把 ./utils 下的三个辅助函数(actionTypesisPlainObjectwarning)看一下:

actionTypes:

const randomString = () =>Math.random().toString(36).substring(7).split('').join('.')const ActionTypes = {INIT: `@@redux/INIT${randomString()}`,REPLACE: `@@redux/REPLACE${randomString()}`,PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}export default ActionTypes
复制代码

这里返回的都是随机的 action.type,为了区别常规业务开发写的 action.type,比如:ActionTypes.INIT 拿到的是一个类似与 @@redux/INITg.f.m.0.0.4 随机字符串,只有这样奇奇怪怪的随机数才不会和业务中定义的 reducer 所判断的 type 重复。


isPlainObject:

判断函数是否是纯对象,[1,23]new Date()这些都会返回 false

export default function isPlainObject(obj) {if (typeof obj !== 'object' || obj === null) return falselet proto = objwhile (Object.getPrototypeOf(proto) !== null) {proto = Object.getPrototypeOf(proto)}return Object.getPrototypeOf(obj) === proto
}
复制代码

warning:

就是一个报错函数

export default function warning(message) {/* eslint-disable no-console */if (typeof console !== 'undefined' && typeof console.error === 'function') {console.error(message)}/* eslint-enable no-console */try {// This error was thrown as a convenience so that if you enable// "break on all exceptions" in your console,// it would pause the execution at this line.throw new Error(message)} catch (e) {} // eslint-disable-line no-empty
}
复制代码

dispatch

dispatch 用过 redux 的都知道,这就是派发 action 的函数,把派发出去的 action 交由 reducer 处理。

function dispatch(action) {if (!isPlainObject(action)) {// action不是纯对象报错throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}if (typeof action.type === 'undefined') {// action没有type属性也报错throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}if (isDispatching) {// 这个store正在dispach别的action的时候不能再dispatch另外一个actionthrow new Error('Reducers may not dispatch actions.')}try {// 当前state和action交由当前的reducer处理// 同时改变isDispatching 为 true 表明正在处理action中,不能dispatch新的action了isDispatching = truecurrentState = currentReducer(currentState, action)} finally {// 修改为 false ,可以dispatch新的actionisDispatching = false}// 赋值,最终 listeners 、 currentListeners 、nextListeners的值都是 nextListenersconst listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {// 遍历调用监听的函数const listener = listeners[i]listener()}// 返回这个action, 没什么作用return action
}
复制代码

核心代码就是 currentState = currentReducer(currentState, action),传入 currentStateactioncurrentReducercurrentReducer 把返回值赋值给了 currentState

subscribe

订阅监听器。

  function subscribe(listener) {if (typeof listener !== 'function') {// 不给函数就报错throw new Error('Expected the listener to be a function.')}if (isDispatching) {// 正在dispatch一个store的时候是不能订阅监听器的throw new Error('You may not call store.subscribe() while the reducer is executing. ' +'If you would like to be notified after the store has been updated, subscribe from a ' +'component and invoke store.getState() in the callback to access the latest state. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 给unsubscribe调用解除订阅标识let isSubscribed = true// 下面解释为什么要调用这个ensureCanMutateNextListeners函数ensureCanMutateNextListeners()// 就是简单的把传入的listeners放到nextListenersnextListeners.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. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}isSubscribed = falseensureCanMutateNextListeners()const index = nextListeners.indexOf(listener)// 从 nextListeners 数组中移除nextListeners.splice(index, 1)}}
复制代码

订阅没什么问题,就是为啥用调用 ensureCanMutateNextListeners 呢? 看一下这个函数:

 function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()}}
复制代码

这个函数就是检查 nextListenerscurrentListeners 是否是相同的,如果是相同的就把 currentListeners 拷贝一个新的赋值给nextListeners。因为数组是引用类型的关系,如果 nextListenerscurrentListeners 相同,像 nextListenerspush 新的 listener 的时候会直接影响到 currentListeners 的值。

注意到另外一点,在 dispatch 函数的最后遍历 listeners 的时候,是这样操作的: const listeners = (currentListeners = nextListeners),这里 nextListenerscurrentListeners 就相同了。

那么为啥内部需要有 currentListenersnextListeners,主要是通知订阅者的过程中发生了其他的订阅(subscribe)和退订(unsubscribe),那肯定会发生错误或者不确定性。

这里有一篇文章论述到这个问题。

getState

简单的把 storecurrentState 返回出来。

  function getState() {if (isDispatching) {throw new Error('You may not call store.getState() while the reducer is executing. ' +'The reducer has already received the state as an argument. ' +'Pass it down from the top reducer instead of reading it from the store.')}return currentState}
复制代码

replaceReducer

这个 API 帮你替换把原来的 reducer 替换成新的 reducer

 function replaceReducer(nextReducer) {if (typeof nextReducer !== 'function') {throw new Error('Expected the nextReducer to be a function.')}// nextReducer替换旧的reducercurrentReducer = nextReducer// 注意这里也dispatch了一个随机action,和createStore的最后dispatch一个随机的初始化action功能是相同的,都是了初始化statedispatch({ type: ActionTypes.REPLACE })}
复制代码

observable

不懂,还是贴一下代码:

  function observable() {const outerSubscribe = subscribereturn {/*** The minimal observable subscription method.* @param {Object} observer Any object that can be used as an observer.* The observer object should have a `next` method.* @returns {subscription} An object with an `unsubscribe` method that can* be used to unsubscribe the observable from the store, and prevent further* emission of values from the observable.*/subscribe(observer) {if (typeof observer !== 'object' || observer === null) {throw new TypeError('Expected the observer to be an object.')}function observeState() {if (observer.next) {observer.next(getState())}}observeState()const unsubscribe = outerSubscribe(observeState)return { unsubscribe }},[$$observable]() {return this}}}
复制代码

4. dispatch一个初始化的action

dispatch({ type: ActionTypes.INIT })
复制代码

在最后,dispatch 了一个 type 为随机值的 action, 我们业务的 reducer 中最后没有匹配到对用的 action.type 都会默认返回默认的 state, 而这个默认的 state 往往又在 reducer 函数最开始写的时候已经给好了默认值,这样 dispatchaction 与任何 reducer 都不匹配,所以拿到了所有 reducer 的默认值从而 currentState 就被更新成了 reducer 定义过的默认值。

5. 返回的store对象

把定义好的方法挂载到一个对象上面,这个对象就是 store 对象。

return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}
复制代码

总结

redux 的代码是真的简洁,代码的注释甚至比代码本身还要长,还是非常值得阅读的。

redux之createStore相关推荐

  1. Redux 入门教程(一):基本用法

    一年半前,我写了<React 入门实例教程>,介绍了 React 的基本用法. React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案.有两个方面,它没涉及. 代码结构 ...

  2. react开发教程(十)redux结合react

    描述 Redux 和 React 之间没有关系.Redux 可以搭配 React.Angular 甚至纯 JS.但是 Redux 还是比较适合和 React 搭配的,因为 React 允许你以 sta ...

  3. 【前端】react and redux教程学习实践,浅显易懂的实践学习方法。

    前言 前几天,我在博文[前端]一步一步使用webpack+react+scss脚手架重构项目 中搭建了一个react开发环境.然而在实际的开发过程中,或者是在对源码的理解中,感受到react中用的最多 ...

  4. Redux 学习笔记

    1:首先安装redux: npm install --save redux 2:引入redux : import { createStore } from 'redux'; //首先创建执行函数,Re ...

  5. 【React.js 06】Redux基础知识

    Redux是一个专注于状态管理的库,和React是解耦的,用Angular和Vue都可以使用Redux.其具有一个单一状态,单向数据流的特性. Redux概念 redux有一个store,记录所有的s ...

  6. redux VS mobx (装饰器配合使用)

    前言:redux和mobx都是状态管理器,避免父级到子级再到子子级嵌套单向数据流,可以逻辑清晰的管理更新共享数据.(刷新页面redux储蓄数据即消失) 配置使用装饰器(使用高阶函数包装你的组件): n ...

  7. Redux 莞式教程 之 简明篇

    Redux 简明教程 原文链接(保持更新):https://github.com/kenberkele... 写在前面 本教程深入浅出,配套 简明教程.进阶教程(源码精读)以及文档注释丰满的 Demo ...

  8. 学习 redux 源码整体架构,深入理解 redux 及其中间件原理

    如果觉得内容不错,可以设为星标置顶我的公众号 1. 前言 你好,我是若川.这是学习源码整体架构系列第八篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是 ...

  9. redux异步action_React躬行记(12)——Redux中间件

    Redux的中间件(Middleware)遵循了即插即用的设计思想,出现在Action到达Reducer之前(如图10所示)的位置.中间件是一个固定模式的独立函数,当把多个中间件像管道那样串联在一起时 ...

  10. [译】Redux入门教程(一)

    前言 老外写技术文章真是叼,这是国外的一个程序员写的一个简单易懂,循序渐进的Redux教程,本着共享的精神,就翻译出来给大家一起看,文章最后有链接,不想看我翻译的直接去看原文吧. 下面是原教程的英文目 ...

最新文章

  1. R语言计算F1评估指标实战:F1 score、使用R中caret包中的confusionMatrix()函数为给定的logistic回归模型计算F1得分(和其他指标)
  2. 2021年SDN和NFV的支出将超1580亿美元!
  3. python就业方向及工资-【行情分享】python就业方向与薪资大揭秘
  4. VC++的应用程序框架中各类之间的访问方法
  5. 假如生活欺骗了你!——Leo网上答疑(14)
  6. matlab学习-线性规划
  7. 如何用 Hook 实时处理和保存 Ajax 数据
  8. X64_Xcelera-CL_PX4采集卡测试记录
  9. 1至100之和用c语言表达方式,C语言菜鸟基础教程之求1到100的和
  10. Android项目导入高德地图
  11. 优科豪马冬季SUV轮胎G072的性能特点全解
  12. 010Editor分析
  13. vue 脚手架跨域问题解决
  14. 九宫格切图器(每天一个python小项目)
  15. python log日志打印两遍_python打印log重复问题
  16. 常见的系统架构设计介绍
  17. java8新特性学习笔记之唠唠“匿名内部类与lambda”
  18. 无法连接到已配置的开发web服务器_Power BI 报表服务器极简安装指南
  19. HTTP Error 503. The service is unavailable
  20. ctfshow baby杯 六一快乐 部分MISC WriteUp

热门文章

  1. day25 在继承的背景下属性查找的顺序、组合、多态与接口、鸭子类型
  2. linux下PHP后台配置极光推送问题
  3. Postman安装使用
  4. 面向对象编程时,十条原则:
  5. Delphi 日期函数列表
  6. 查看windows下指定的端口是否开放
  7. P(Y|X) 和 P(X,Y)
  8. Tensorflow的最佳实践
  9. .Net面试葵花宝典
  10. python 开源项目大全