摘要:订阅器不应该关注所有的变化,在订阅器被调用之前,往往由于嵌套的导致发生多次的改变,我们应该保证所有的监听都注册在之前。
前言
用 React + Redux 已经一段时间了,记得刚开始用Redux 的时候感觉非常绕,总搞不起里面的关系,如果大家用一段时间Redux又看了它的源码话,对你的理解会有很大的帮助。看完后,在回来看Redux,有一种 柳暗花明又一村 的感觉 ,.
源码我分析的是用 es6 语法的源码,大家看目录结构,一共有 6 个问件。先说下各个文件大概功能。
applyMiddlewar.js 使用自定义的 middleware 来扩展 Redux
bindActionCreators.js 把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用
combineReducers.js 一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分
compose.js 从右到左来组合多个函数,函数编程中常用到
createStore.js 创建一个 Redux Store 来放所有的state
utils/warnimng.js 控制台输出一个警告,我们可以不用看
我会按每个模块的重要性,去做分析,今天就先把 createStore .js 分享给大家.
createStore.js注释很详细,直接看注释就可以了
// 导入 lodash ,判断是否是普通(plain)对象
import isPlainObject from "lodash/isPlainObject"
//导入 symbol 类型的 observable (symbol类型的属性,是对象的私有属性)
import $$observable from "symbol-observable"
/**
*定义 Redux Action 的初始化 type
*
*/
export var ActionTypes = {
INIT: "@@redux/INIT"
}
/**
* 创建一个Redux store来存放应用所有的 state。应用中有且只有一个store
*
* @param {Function} reducer 是一个函数,接收两个参数,分别是当前的 state 树和
* 要处理的 action,返回新的 state 树
*
* @param {any} 初始化时的state ,在应用中,你可以把服务端传来经过处理后的 state
*传给它。如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入
*的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
*
* @param {Function} enhancer 是一个组合的高阶函数,返回一个强化过的 store creator .
* 这与 middleware相似,它也允许你通过复合函数改变 store 接口。
*
* @returns {Store} 返回一个对象,给外部提供 dispatch, getState, subscribe, replaceReducer,
*/
export default function createStore(reducer, preloadedState, enhancer) {
//判断 preloadedState 是一个函数并且 enhancer 是未定义
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
enhancer = preloadedState // 把 preloadedState 赋值给 enhancer
preloadedState = undefined // 把 preloadedState 赋值为 undefined
}
//判断 enhancer 不是 undefined
if (typeof enhancer !== "undefined") {
//判断 enhancer 不是一个函数
if (typeof enhancer !== "function") {
//抛出一个异常 (enhancer 必须是一个函数)
throw new Error("Expected the enhancer to be a function.")
}
//调用 enhancer ,返回一个新强化过的 store creator
return enhancer(createStore)(reducer, preloadedState)
}
//判断 reducer 不是一个函数
if (typeof reducer !== "function") {
//抛出一个异常 (reducer 必须是一个函数)
throw new Error("Expected the reducer to be a function.")
}
var currentReducer = reducer //把 reducer 赋值给 currentReducer
var currentState = preloadedState //把 preloadedState 赋值给 currentState
var currentListeners = [] //初始化 listeners 数组
var nextListeners = currentListeners//nextListeners 和 currentListeners 指向同一个引用
var isDispatching = false //标记正在进行dispatch
/**
* 保存一份订阅快照
* @return {void}
*/
function ensureCanMutateNextListeners() {
//判断 nextListeners 和 currentListeners 是同一个引用
if (nextListeners === currentListeners) {
//通过数组的 slice 方法,复制出一个 listeners ,赋值给 nextListeners
nextListeners = currentListeners.slice()
}
}
/**
* 获取 store 里的当前 state tree
*
* @returns {any} 返回应用中当前的state tree
*/
function getState() {
//当前的state tree
return currentState
}
/**
*
* 添加一个 listener . 当 dispatch action 的时候执行,这时 sate 已经发生了一些变化,
* 你可以在 listener 函数调用 getState() 方法获取当前的 state
*
* 你可以在 listener 改变的时候调用 dispatch ,要注意
*
* 1. 订阅器(subscriptions) 在每次 dispatch() 调用之前都会保存一份快照。
* 当你在正在调用监听器(listener)的时候订阅(subscribe)或者去掉订阅(unsubscribe),
* 对当前的 dispatch() 不会有任何影响。但是对于下一次的 dispatch(),无论嵌套与否,
* 都会使用订阅列表里最近的一次快照。
*
* 2. 订阅器不应该关注所有 state 的变化,在订阅器被调用之前,往往由于嵌套的 dispatch()
* 导致 state 发生多次的改变,我们应该保证所有的监听都注册在 dispath() 之前。
*
* @param {Function} 要监听的函数
* @returns {Function} 一个可以移除监听的函数
*/
function subscribe(listener) {
//判断 listener 不是一个函数
if (typeof listener !== "function") {
//抛出一个异常 (listener 必须是一个函数)
throw new Error("Expected listener to be a function.")
}
//标记有订阅的 listener
var isSubscribed = true
//保存一份快照
ensureCanMutateNextListeners()
//添加一个订阅函数
nextListeners.push(listener)
//返回一个取消订阅的函数
return function unsubscribe() {
//判断没有订阅一个 listener
if (!isSubscribed) {
//调用 unsubscribe 方法的时候,直接 return
return
}
//标记现在没有一个订阅的 listener
isSubscribed = false
//保存一下订阅快照
ensureCanMutateNextListeners()
//找到当前的 listener
var index = nextListeners.indexOf(listener)
//移除当前的 listener
nextListeners.splice(index, 1)
}
}
/**
* dispath action。这是触发 state 变化的惟一途径。
*
* @param {Object} 一个普通(plain)的对象,对象当中必须有 type 属性
*
* @returns {Object} 返回 dispatch 的 action
*
* 注意: 如果你要用自定义的中间件, 它可能包装 `dispatch()`
* 返回一些其它东西,如( Promise )
*/
function dispatch(action) {
//判断 action 不是普通对象。也就是说该对象由 Object 构造函数创建
if (!isPlainObject(action)) {
//抛出一个异常(actions 必须是一个普通对象. 或者用自定义的中间件来处理异步 actions)
throw new Error(
"Actions must be plain objects. " +
"Use custom middleware for async actions."
)
}
// 判断 action 对象的 type 属性等于 undefined
if (typeof action.type === "undefined") {
//抛出一个异常
throw new Error(
"Actions may not have an undefined "type" property. " +
"Have you misspelled a constant?"
)
}
//判断 dispahch 正在运行,Reducer在处理的时候又要执行 dispatch
if (isDispatching) {
//抛出一个异常(Reducer在处理的时候不能 dispatch action)
throw new Error("Reducers may not dispatch actions.")
}
try {
//标记 dispatch 正在运行
isDispatching = true
//执行当前 Reducer 函数返回新的 state
currentState = currentReducer(currentState, action)
} finally { // (try catch) 最终会被执行的地方
//标记 dispatch 没有在运行
isDispatching = false
}
//所有的的监听函数赋值给 listeners
var listeners = currentListeners = nextListeners
//遍历所有的监听函数
for (var i = 0; i < listeners.length; i++) {
// 执行每一个监听函数
listeners[i]()
}
//返回传入的 action 对象
return action
}
/**
* 替换计算 state 的 reducer。
*
* 这是一个高级 API。
* 只有在你需要实现代码分隔,而且需要立即加载一些 reducer 的时候才可能会用到它。
* 在实现 Redux 热加载机制的时候也可能会用到。
*
* @param {Function} store 要用的下一个 reducer.
* @returns {void}
*/
function replaceReducer(nextReducer) {
//判断 nextReducer 不是一个函数
if (typeof nextReducer !== "function") {
//抛出一个异常 (nextReducer必须是一个函数)
throw new Error("Expected the nextReducer to be a function.")
}
//当前传入的 nextReducer 赋值给 currentReducer
currentReducer = nextReducer
//调用 dispatch 函数,传入默认的 action
dispatch({ type: ActionTypes.INIT })
}
/**
* 在 creatorStore 内部没有看到此方法的调用
* (猜想 : 作者可能想用比较强大,活跃的 observable 库替换现在的 publish subscribe)
*
* @returns {observable} 状态改变的时候返回最小的 observable.
* 想要了解跟多关于 observable 库,建议看下
* https://github.com/zenparsing/es-observable (标准 es Observable)
*/
function observable() {
//订阅方法赋值给变量 outerSubscribe
var outerSubscribe = subscribe
return {
/**
* 这是一个最小的观察订阅方法
*
* @param {Object} 观察着的任何一个对象都可以作为一个 observer.
* 观察者应该有 `next` 方法
*/
subscribe(observer) {
//判断 observer 是一个对象
if (typeof observer !== "object") {
//抛出异常
throw new TypeError("Expected the observer to be an object.")
}
//获取观察着的状态
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
//返回一个取消订阅的方法
var unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
//对象的私有属性,暂时不知道有什么用途
[$$observable]() {
return this
}
}
}
//reducer 返回其初始状态
//初始化 store 里的 state tree
dispatch({ type: ActionTypes.INIT })
//返回 store 暴漏出的接口
return {
dispatch, //唯一一个可以改变 state 的哈按时
subscribe, //订阅一个状态改变后,要触发的监听函数
getState, // 获取 store 里的 state
replaceReducer, //Redux热加载的时候可以替换 Reducer
[$$observable]: observable //对象的私有属性,供内部使用
}
}
结束语
代码已经放在 githunb 上,大家可以查看 https://github.com/jiechud/redux-source-analyze , 如果对你有帮助,麻烦请 Star 一下吧.
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/88051.html
摘要:找工作之前看了很多面试题,复习资料,但是发现纯看面试题是不行的,因为靠背的东西是记不牢的,需要知识成体系才可以,所以笔者整理了一份复习大纲,基本涵盖了中高级工程师面试所必须知识点,希望可以通过此文帮助一些想换工作的朋友更好的复习,准备面试。 概述 都说金三银四青铜五,这几个月份是程序员最好的跳槽时间,笔者四月初也换了工作。找工作之前看了很多面试题,复习资料,但是发现纯看面试题是不行的,因为靠...
摘要:在严格模式下调用函数则不影响默认绑定。回调函数丢失绑定是非常常见的。因为直接指定的绑定对象,称之为显示绑定。调用时强制把的绑定到上显示绑定无法解决丢失绑定问题。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导) 本周正式开始前端进阶的第三期,本周的主题是this全面解析,今天是第9天。 本计划一共28期,每期重点攻克一个面试重...
摘要:层确保数据一致性和可靠性。保证集群的相关组件在同一时刻能够达成一致,相当于集群的领导层,负责收集更新和发布集群信息。元数据服务器,跟踪文件层次结构并存储只供使用的元数据。启迪云-高级开发工程师 侯玉彬前言上一次简单的介绍Ceph的过去和未来的发展。这一节将详细介绍Ceph的构件以及组件。Ceph存储架构Ceph 存储集群由几个不同的daemon组成,每个daemon负责Ceph 的一个独特...
摘要:入冬了,寒风呼啸,白雪飘飘,此刻窝在家里学习应当是极好的。为了满足大家的需求,小编火速为大家整理了史上最全的资料。 showImg(https://segmentfault.com/img/remote/1460000007586577?w=900&h=500); 入冬了,寒风呼啸,白雪飘飘,此刻窝在家里学习应当是极好的。为了满足大家的需求,小编火速为大家整理了史上最全的Docker资...
阅读 3270·2021-11-02 14:40
阅读 1037·2019-08-30 15:53
阅读 1495·2019-08-30 15:53
阅读 3431·2019-08-30 13:53
阅读 3509·2019-08-29 12:50
阅读 1309·2019-08-26 13:49
阅读 2050·2019-08-26 12:20
阅读 3868·2019-08-26 11:33