redux之createStore
回顾一下 redux
的目录结构:
.\REDUX\SRC │ applyMiddleware.js │ bindActionCreators.js │ combineReducers.js │ compose.js │ createStore.js │ index.js │ └─utils actionTypes.js isPlainObject.js warning.js
redux
在 index.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
的三个参数 reducer
、 preloadedState
、 enhancer
的顺序调整。
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
当前store
的reducer
,由createStore
传入的第一个参数reducer
初始化currentState
保存当前整个state
的状态,初始值就是createStore
传进来的第二个参数preloadedState
,相当于store
的初始值currentListeners
当前的监听器,默认是空nextListeners
下一个监听器,由currentListeners
赋值isDispatching
当前的store
是否正在dispatch
一个action
全是闭包保存的变量
3. 函数的定义
在 createStore
的最后,dispatch
了一个 { type: ActionTypes.INIT }
对象,那就按图索骥,从 dispatch
函数开始看。
先把 ./utils
下的三个辅助函数(actionTypes
、 isPlainObject
、warning
)看一下:
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)
,传入 currentState
、action
给 currentReducer
,currentReducer
把返回值赋值给了 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()}}
复制代码
这个函数就是检查 nextListeners
和 currentListeners
是否是相同的,如果是相同的就把 currentListeners
拷贝一个新的赋值给nextListeners
。因为数组是引用类型的关系,如果 nextListeners
和 currentListeners
相同,像 nextListeners
中 push
新的 listener
的时候会直接影响到 currentListeners
的值。
注意到另外一点,在 dispatch
函数的最后遍历 listeners
的时候,是这样操作的: const listeners = (currentListeners = nextListeners)
,这里 nextListeners
和 currentListeners
就相同了。
那么为啥内部需要有 currentListeners
和 nextListeners
,主要是通知订阅者的过程中发生了其他的订阅(subscribe
)和退订(unsubscribe
),那肯定会发生错误或者不确定性。
这里有一篇文章论述到这个问题。
getState
简单的把 store
的 currentState
返回出来。
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
函数最开始写的时候已经给好了默认值,这样 dispatch
的 action
与任何 reducer
都不匹配,所以拿到了所有 reducer
的默认值从而 currentState
就被更新成了 reducer
定义过的默认值。
5. 返回的store对象
把定义好的方法挂载到一个对象上面,这个对象就是 store
对象。
return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}
复制代码
总结
redux
的代码是真的简洁,代码的注释甚至比代码本身还要长,还是非常值得阅读的。
redux之createStore相关推荐
- Redux 入门教程(一):基本用法
一年半前,我写了<React 入门实例教程>,介绍了 React 的基本用法. React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案.有两个方面,它没涉及. 代码结构 ...
- react开发教程(十)redux结合react
描述 Redux 和 React 之间没有关系.Redux 可以搭配 React.Angular 甚至纯 JS.但是 Redux 还是比较适合和 React 搭配的,因为 React 允许你以 sta ...
- 【前端】react and redux教程学习实践,浅显易懂的实践学习方法。
前言 前几天,我在博文[前端]一步一步使用webpack+react+scss脚手架重构项目 中搭建了一个react开发环境.然而在实际的开发过程中,或者是在对源码的理解中,感受到react中用的最多 ...
- Redux 学习笔记
1:首先安装redux: npm install --save redux 2:引入redux : import { createStore } from 'redux'; //首先创建执行函数,Re ...
- 【React.js 06】Redux基础知识
Redux是一个专注于状态管理的库,和React是解耦的,用Angular和Vue都可以使用Redux.其具有一个单一状态,单向数据流的特性. Redux概念 redux有一个store,记录所有的s ...
- redux VS mobx (装饰器配合使用)
前言:redux和mobx都是状态管理器,避免父级到子级再到子子级嵌套单向数据流,可以逻辑清晰的管理更新共享数据.(刷新页面redux储蓄数据即消失) 配置使用装饰器(使用高阶函数包装你的组件): n ...
- Redux 莞式教程 之 简明篇
Redux 简明教程 原文链接(保持更新):https://github.com/kenberkele... 写在前面 本教程深入浅出,配套 简明教程.进阶教程(源码精读)以及文档注释丰满的 Demo ...
- 学习 redux 源码整体架构,深入理解 redux 及其中间件原理
如果觉得内容不错,可以设为星标置顶我的公众号 1. 前言 你好,我是若川.这是学习源码整体架构系列第八篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是 ...
- redux异步action_React躬行记(12)——Redux中间件
Redux的中间件(Middleware)遵循了即插即用的设计思想,出现在Action到达Reducer之前(如图10所示)的位置.中间件是一个固定模式的独立函数,当把多个中间件像管道那样串联在一起时 ...
- [译】Redux入门教程(一)
前言 老外写技术文章真是叼,这是国外的一个程序员写的一个简单易懂,循序渐进的Redux教程,本着共享的精神,就翻译出来给大家一起看,文章最后有链接,不想看我翻译的直接去看原文吧. 下面是原教程的英文目 ...
最新文章
- R语言计算F1评估指标实战:F1 score、使用R中caret包中的confusionMatrix()函数为给定的logistic回归模型计算F1得分(和其他指标)
- 2021年SDN和NFV的支出将超1580亿美元!
- python就业方向及工资-【行情分享】python就业方向与薪资大揭秘
- VC++的应用程序框架中各类之间的访问方法
- 假如生活欺骗了你!——Leo网上答疑(14)
- matlab学习-线性规划
- 如何用 Hook 实时处理和保存 Ajax 数据
- X64_Xcelera-CL_PX4采集卡测试记录
- 1至100之和用c语言表达方式,C语言菜鸟基础教程之求1到100的和
- Android项目导入高德地图
- 优科豪马冬季SUV轮胎G072的性能特点全解
- 010Editor分析
- vue 脚手架跨域问题解决
- 九宫格切图器(每天一个python小项目)
- python log日志打印两遍_python打印log重复问题
- 常见的系统架构设计介绍
- java8新特性学习笔记之唠唠“匿名内部类与lambda”
- 无法连接到已配置的开发web服务器_Power BI 报表服务器极简安装指南
- HTTP Error 503. The service is unavailable
- ctfshow baby杯 六一快乐 部分MISC WriteUp