Redux源码浅析系列(一):`CreateStore`
使用react
+redux
开发有一段时间了,刚开始使用并没有深入了解其源码,最近静下心来,阅读了一下,感触颇深。 本系列主要从createStore
,combineReducer
,compose
,applyMiddleware
几个方面进行讲解。本系列不会详细讲解redux
的用法,因此可能更适合有redux
使用经验的小伙伴们阅读。
store
是redux
中用来存储所有state
树的一个对象,改变store
内的状态的唯一方法时对它dispatch
一个action
。 redux
中通过createStore
创建一个store
,用来存放应用中所有状态树,一个应用中应该有且只有一个store
。 首先,我们先来回顾一下createStore
的用法: 用法: createStore(reducer, [preloadedState], enhancer)
源码标注解读
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'export const ActionTypes = {INIT: '@@redux/INIT'
}export default function createStore(reducer, preloadedState, enhancer) {//preloadedState是可选的if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}//通过`enhancer`重新构建一个增强过的`store`。if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)}if (typeof reducer !== 'function') {throw new Error('Expected the reducer to be a function.')}let currentReducer = reducerlet currentState = preloadedStatelet currentListeners = []let nextListeners = currentListenerslet isDispatching = false/** * 订阅器在每次`dispatch`调用之前都会保存一份快照。当你正在调用`listener`的时候,`subscribe`或者`unSubscribe`,* 对当前的`dispatch`都不会有影响。* 但是,对于下一次`dispatch`,不管是否嵌套,都会使用`subscribe`列表中最近的一次快照。*/function ensureCanMutateNextListeners() {if (nextListeners === currentListeners) {nextListeners = currentListeners.slice()}}function getState() {return currentState}//注册一个`change listener`,每当`dispatch`一个`action`的时候就会触发这个`listener`。//同时,返回一个`unsubscribe`函数,用来取消注册监听函数。function subscribe(listener) {if (typeof listener !== 'function') {throw new Error('Expected listener to be a function.')}let isSubscribed = trueensureCanMutateNextListeners()nextListeners.push(listener)return function unsubscribe() {//未注册 ,返回if (!isSubscribed) {return}isSubscribed = falseensureCanMutateNextListeners()const index = nextListeners.indexOf(listener)//从`listener list`中剔除当前`listener`。nextListeners.splice(index, 1)}}//分发`action`,这是改变`store`中`state`的唯一方式。function dispatch(action) {if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}if (isDispatching) {throw new Error('Reducers may not dispatch actions.')}try {isDispatching = true//使用`getState`的结果和传入的`action`以同步的方式调用`store`的`reduce`函数,返回值会被作为下一个`state`.//currentReducer ==> rootReducercurrentState = currentReducer(currentState, action)} finally {isDispatching = false}//确保即使在 `listener`中进行`subscribe`或者`unSubscribe`,对当前的`dispatch`也不会有任何的影响。const listeners = currentListeners = nextListeners//遍历调用各个`listener`for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}return action}//替换 `store` 当前用来计算 `state` 的 `reducer`function replaceReducer(nextReducer) {if (typeof nextReducer !== 'function') {throw new Error('Expected the nextReducer to be a function.')}currentReducer = nextReducerdispatch({ type: ActionTypes.INIT })}function observable() {const outerSubscribe = subscribereturn {subscribe(observer) {if (typeof observer !== 'object') {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}}}dispatch({ type: ActionTypes.INIT })return {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}
}复制代码
针对,subscribe
和dispatch
这两个方法,有几点需要注意的地方:
1.虽然在注册的listener
里面调用dispatch
在技术上可行的,但是由于每一次dispatch
一个action
的时候都会造成所有listener
的执行,而listener
在执行的时候又会进行dispatch
的调用,这样可能会陷入一个无穷的循环当中。 2.关于函数ensureCanMutateNextListeners
的作用。我在刚开始看redux
的时候,曾一度很纠结ensureCanMutateNextListeners
这个函数的作用,感觉明明可以直接用currentListeners
的,为什么非得多此一举把它进行一份拷贝呢? 后来,在阅读了redux
的api
之后,发现了这样一句话。 The subscriptions are snapshotted just before every dispatch() call. If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on the dispatch() that is currently in progress. However, the next dispatch() call, whether nested or not, will use a more recent snapshot of the subscription list.
大概翻译一下,就是说: 订阅器(subscriptions) 在每次 dispatch() 调用之前都会保存一份快照。当你在正在调用监听器(listener)的时候订阅(subscribe)或者去掉订阅(unsubscribe),对当前的 dispatch() 不会有任何影响。但是对于下一次的 dispatch(),无论嵌套与否,都会使用订阅列表里最近的一次快照。
可能还有点迷茫,那我们来举个例子说明一下: 下面是目前redux dispatch
的源码:
function subscribe(listener){...ensureCanMutateNextListeners()nextListeners.push(listener)...
}
function dispatch(action){...const listeners = currentListeners = nextListeners//确保即使在 listener中进行subscribe或者unSubscribe,对当前的dispatch也不会有任何的影响。for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}...
}
复制代码
假如我们不使用nextListeners
这个快照的话,那么代码会变成下面这个样子:
function subscribe(listener){...currentListeners.push(listener)...
}
function dispatch(action){...for (let i = 0; i < currentListeners.length; i++) {const listener = currentListeners[i]listener()}...
}
复制代码
注意到什么了吗?还没有?好,那我们继续往下看: 假如,我们设置了这样一个listener
:
function listenerA(){store.subscribe(listenerA);
}
复制代码
那么,当我们dispatch
一个action
之后,会发生什么?
首先,它会触发所有的listener
,假设我们当前就只有这么一个。那么currentListeners.length===1
,然后在执行这个listener
的时候,它又会绑定一个新的listener
。这时候currentListeners.length===2
,for
循环会继续执行下一个listener
,然后currentListeners.length===3
...这样,就会陷入一个死循环。
当我们使用了nextListeners
这个快照之后,情况又如何呢?
同样,当我们dispatch
一个action
之后,它会触发所有的listener
,然后在执行这个listener
的时候,又会绑定一个新的listener
。好,到此一次循环结束。这时候来看一下listeners.length
的值,因为有了nextListeners
这个快照的存在,listeners.length
的值还是1。不管我们在listener
中做什么样的操作,subscribe
还是unsubscribe
,对当前的dispatch
都是没有影响的。 而下一次的listeners
又是最近一次listener list
的快照的值。
转载于:https://juejin.im/post/5a9d17ef6fb9a028df221ef9
Redux源码浅析系列(一):`CreateStore`相关推荐
- Redux源码浅析系列(二):`combineReducer`
上一章,我们讲解了createStore.下面,我们来看一下combineReducer. 在redux中,我们禁止在应用中创建多个store(我们这里默认讨论的都是客户端应用,同构应用不适用这条规则 ...
- redux源码分析之一:createStore.js
欢迎关注redux源码分析系列文章: redux源码分析之一:createStore.js redux源码分析之二:combineReducers.js redux源码分析之三:bindActionC ...
- Redux 源码解析系列(一) -- Redux的实现思想
文章来源: IMweb前端社区 黄qiong(imweb.io) IMweb团队正在招聘啦,简历发至jayccchen@tencent.com Redux 其实是用来帮我们管理状态的一个框架,它暴露给 ...
- redux源码分析之二:combineReducers.js
欢迎关注redux源码分析系列文章: redux源码分析之一:createStore.js redux源码分析之二:combineReducers.js redux源码分析之三:bindActionC ...
- 逐行阅读redux源码(二)combineReducers
前情提要 逐行阅读redux源码(一)createStore 认识reducers 在我们开始学习源码之前,我们不妨先来看看何谓reducers: 如图所见,我们可以明白, reducer 是用来对初 ...
- 学习 redux 源码整体架构,深入理解 redux 及其中间件原理
如果觉得内容不错,可以设为星标置顶我的公众号 1. 前言 你好,我是若川.这是学习源码整体架构系列第八篇.整体架构这词语好像有点大,姑且就算是源码整体结构吧,主要就是学习是代码整体结构,不深究其他不是 ...
- Redux源码分析(一)
Redux源码分析(createStore) 使用redux都快3年了,到现在也没认真去了解一下源码罪过啊,所以需要对它进行一些分析和学习,一方面能更好的去使用它,另一方面也学习一下该框架的设计思路, ...
- Redux源码全篇浅读
本文是关于 redux(3.7.2)源代码的一些浅读 在redux源码目录中 ,可以看到以下文件目录: |-- utils/|-- warning.js //打印error |-- 1. applyM ...
- 深入理解redux之从redux源码到react-redux的原理
在使用react的过程中,用redux来管理应用中的状态,使应用流更清晰的同时也会有小小的疑惑,比如reducer在redux中时怎么发挥作用的,为什么只要写好reducer,和dispatch特定a ...
最新文章
- 序列内置方法详解(string/list/tuple)
- mysql批量创建数据库 导数据
- 在应用了皮肤的程序中制作透明的文本编辑控件(如:TcxMemo)
- 在access窗体中加图片_Access实战:一种不用按钮控件就能控制子窗体联动的方法...
- 全球及中国磁性分离滑轮行业竞争战略及未来产销需求预测报告2022版
- 【Linux系统编程】POSIX无名信号量
- C# 字符,字符串和文本处理。
- 24--反转字符串中的单词 III
- MATLAB把多行多列矩阵数据和文字写入txt文件
- Android圆角图片封装类--copy别人的,不能转载,我也就醉了,谢谢原创
- Openstack Object Store(Swift)设置公有存储的方法
- 组装电脑多少钱一台_客户花9000元组装一台电脑,奸商赚5000块,利润真吓人
- mysql之查询某段时间范围的数据
- 对比不同的JSON字符串或者对象转的字符串
- 移动安全-IOS逆向第三天——实战HOOK RSA/DES加密
- 蚂蚁小程序--自学笔记
- 常见的安全产品与服务整理
- jQuery插件的使用
- 对隐秘的角落电视剧的感悟以及认识
- JBoss学习和应用