redux源码浅入浅出
运用redux有一段时间了,包括redux-thunk和redux-saga处理异步action都有一定的涉及,现在技术栈转向阿里的dva+antd,好用得不要不要的,但是需要知己知彼要对react家族有一点源码上的深入了,就从redux开始吧。
redux源码是那么简洁、清晰、干净,让我忍不住一口气全部看完了还意犹未尽的写一篇随笔,mark一下,过段时间回头再重新细细评味学习一波。原谅我把整片代码贴出来,因为我懒啊,我会尽量把代码注解写详细一点。
index
redux对外暴露出的api,这里可以看出文件结构和功能块相关分得很清晰。
import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes' /*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
// isCrushed 函数仅仅用于判断代码是否处于压缩并再压缩时抛出警告
function isCrushed() {} if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
} export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
createStore
redux最重要的api,用于构建Store,并在创建以后还有自己的api,结构十分的清晰
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject' /**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
* @param {Function} reducer:返回一个完整独立全新的state tree,接受参数(当前state,需要触发actions集合)
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
* @param {any} [preloadedState] 初始化state,不是必需,可以与服务端渲染水合初始状态,
* 如果使用combineReduers必需与其中key值一一对应,查看combineReduers实现
*
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
* @param {Function} [enhancer] store的外挂,常用middleware中间件,其他暂时不去深入
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) { // 判断参数个数,类似jq===on参数处理方式
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
} // 首先判断enhancer(常见的便是middlewares中间件),循环回调将跳过此处
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// middlewares详细解释返回值,
return enhancer(createStore)(reducer, preloadedState)
}
// redux为了方便开发者做了很多友好的提示,只有深入源码才知道的良苦用心,reducer只接受是一个函数
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
} // 保存当前的传入值,后边会涉及到这些值的来回更迭
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
// 监听函数事件队列 为什么不写成let nextListeners = currentListeners = [] 风格吗?
// 还有为什么需要两个listener数组来存放呢?答案再订阅和dispatch里面
let nextListeners = currentListeners
// 是否处于dispatch过程中,我也好奇异步dispatch的时候将怎么变化
let isDispatching = false // 当前监听队列与接下来的监听队列指向同一个数组时,slice出新的数组
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 还是为了去除引用,完成next和current的交替,可以将next看作是current的快照
nextListeners = currentListeners.slice()
}
} /**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
// 只有在非触发状态才能通过api获取当前state的快照
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.'
)
}
// 注意这里闭包了,直接给里currentState,且他是时常变化的值,需要再稳定的时候取值
return currentState
} /**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. 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.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
// dva里面也有监听器,下次去看看源码
function subscribe(listener) {
// 老规矩容错
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
} if (isDispatching) {
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.'
)
}
// 监听已经完成标志,用于清除监听
let isSubscribed = true
// 函数入其名,得到nextListeners
ensureCanMutateNextListeners()
// 将监听的事件添加到nextListeners队列中,注意可能添加了队列中已有的事件,不管执行两遍
nextListeners.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 = false
// 再次得到新的nextListeners
ensureCanMutateNextListeners()
// 感觉这里如果注册两个相同的事件,会移除前面那个,不知道会不会有问题
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
} /**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
// 相当重要的方法,纯粹的dispatch的参数只接受Object类型的,thunk就是对它进行处理进而能传入
// function用回调的形式重新dispatch,下次再详细thunk和saga
function dispatch(action) {
// isPlainObject用于判断是否是对象
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// action关键字限制为 type,为了不造成命名上的困惑一般type前缀我会设置与文件夹同名
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
} // 正在dispatch,这里什么情况会出现这个警告呢!!!
// 在dispatch中嵌套的调用dispatch会触发这类警告,可能是担心dispatchA(dispatchB(dispatchA))的嵌套循环问题把
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
} try {
isDispatching = true
// 进行reduce操作,记得参数是当前state和action对象,返回全新的State对象,这一手操作是react就高兴了
currentState = currentReducer(currentState, action)
} finally {
//完成一波reducer记得复位标志,表示我的完成dispatch。
isDispatching = false
} // 执行事件队列前才拿到最新的listenters,在此之前可能会出现订阅与退订的嵌套等问题,暂存的nextlisteners可以保证dispatch的正常执行
// 假如出现listenerA(){store.subscribe(listenerA);}的嵌套情况,listeners的长度将再每一次执行延长一直至无限长
// 当然如果采用len = listeners.length;直接固定循环次数可以解决现在的情况,但是退订等事件的发生也会出现问题,所以暂存是最安全的做法
const listeners = (currentListeners = nextListeners)
// 为什么要用for循环不用foreach,想想forEach对空元素的处理的性能问题把
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 为什么不直接listeners[i]()执行呢?而是负值单独调用呢?
// 赋值之后this的指向不再是listens而是window
listener()
} // 返回了整个action对象
return action
} /**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/ // 替换reducer函数
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
} currentReducer = nextReducer
// 触发私有的replace action
dispatch({ type: ActionTypes.REPLACE })
} /**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
// 可以看作是对redux观察者的一个扩展,可作为全局的每次dispatch都执行方法入口
function observable() {
const outerSubscribe = subscribe
return {
/**
* 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.
*/
// 需要传入一个带next方法的对象,将返回退订钩子
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
} function observeState() {
if (observer.next) {
// next方法将获得当时的store
observer.next(getState())
}
} observeState()
const unsubscribe = outerSubscribe(observeState)
// 返回包含退订对象
return { unsubscribe }
},
// 用于获取observeable,这名字取的。。。
[$$observable]() {
return this
}
}
} // When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
// 初始化store对象
dispatch({ type: ActionTypes.INIT }) return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
compose
一个关于reduce的函数设计,需要特别拿出来说说
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/ export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
} if (funcs.length === 1) {
return funcs[0]
}
// reduce用在这里太巧妙了,奇妙的洋葱函数,好吧也没那么奇妙
// compose(f, g, h)(...args) 等同于 f(g(h(...args)))在后面会形成一个currying函数
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
applyMiddleware
重点,废话不多说直接上代码
import compose from './compose' /**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
// 非常精髓的一段代码
// createStore中以enhancer(createStore)(reducer, preloadedState)调用
export default function applyMiddleware(...middlewares) {
// 二阶函数参数...args对应reducer, preloadedState
return createStore => (...args) => {
const store = createStore(...args)
// 这里不应该是 const dispatch = store.dispatch??有些版本出现这样
// 猜测:这里避免使用到没有中间件处理过的disptch,后面将传入完整的store.dispatch作为根参数,
// 求解如果这里只是个警告函数,每个中间件接受到的({ dispatch, getState })又是什么呢?
// 好吧,我又又想到答案了,再下面
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
// 中间件获取到的能力,获取store快照(isDispatching???怎么判断的),触发reducer
const middlewareAPI = {
getState: store.getState,
// 我就是上面的答案:这里dispatch用闭包并不是直接的引用,dispatch会根据dispatch = compose(...chain)(store.dispatch)
// 而变化,在此之前调用dispatch会爆出警告!!!
dispatch: (...args) => dispatch(...args)
}
// middleware应该是高阶函数,return 了一个function在chain数组
// 对应thunk的createThunkMiddleware({dispatch, getStat}),这里只要注意传入了什么,thunk内详细分析怎么运行中间件
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 将store.dispatch作为二阶参数传入,最终将对应中间件最内层的action,
// 注意下面这个例子:
// applyMiddleware(log1, log2, log3),在这里通过洋葱函数的处理dispatch变成log11(log22(log33(store.dispatch)))这样一个函数
// log11是log1({dispatch, getState})的返回函数,以此类推,这种结构也限定里中间件函数的基本结构是
// ({ dispatch, getState }) => next => action => {} ,最开始可能对这个结构很迷糊,why,看下面
// 这里就形成一个第一个参数为store.dispatch的currying函数,之后传入的action,dispatch(action)都将一并视为compose(...chain)(store.dispatch)(action)
dispatch = compose(...chain)(store.dispatch) // 对应返回在了createStore里即Store,全新的dispatch诞生
return {
...store,
dispatch
}
}
} // 觉得把redux-thunk的代码一起贴出来才有参照性
function createThunkMiddleware(extraArgument) {
// 其实thunk内容实在是简洁,判断类型将dispatch放入到函数里面,这里的dispatch是层层包装过的
// 那么我们来分析针对整个箭头函数和中间件结构进行分析一下
// log11的next(action)对应log22(log33(action)),
// log22的next(action)对应log33(action),
// log33的next对应store.dispatch,最后返回一个需要传参为action的函数,
// action对应{type: 'TO_DO',text: '1'}一直传递无变化,只有next变化,形成一个层层执行
//
// 而执行顺序有点像冒泡,从外到里再从里到外,如果上面的log每个都有before和after的话,顺序将是
// log11.before > log22.before > log33.before > store.dispatch > log33.after > log22.after > log11.after > end
// 每一个中间件将对dispatch之前和之后作些动作
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
} return next(action);
};
} const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware; // export default thunk; // 调用方式 createStore(reducer, applyMiddleware(thunk))
其他还有几个文件就不贴出来了,只有深入到源码才能感受代码之美,redux简直称为精粹。redux就是典型的百行代码千行文档,也只有看了源码才能略微理解其用途和技巧。我很清楚上面的注解十分的混乱,有的地方描述肯定有误,也没能力三言两语把一个经典框架描述得清楚,敬请谅解,与君共勉。
redux源码浅入浅出的更多相关文章
- 浅入深出之Java集合框架(中)
Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- Flink 反压 浅入浅出
前言 微信搜[Java3y]关注这个朴实无华的男人,点赞关注是对我最大的支持! 文本已收录至我的GitHub:https://github.com/ZhongFuCheng3y/3y,有300多篇原创 ...
- 浅入浅出 1.7和1.8的 HashMap
前言 HashMap 是我们最最最常用的东西了,它就是我们在大学中学习数据结构的时候,学到的哈希表这种数据结构.面试中,HashMap 的问题也是常客,现在卷到必须答出来了,是必须会的知识. 我在学习 ...
- 浅入深出之Java集合框架(下)
Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...
- 浅入深出Vue:环境搭建
浅入深出Vue:环境搭建 工欲善其事必先利其器,该搭建我们的环境了. 安装NPM 所有工具的下载地址都可以在导航篇中找到,这里我们下载的是最新版本的NodeJS Windows安装程序 下载下来后,直 ...
- 浅入深出Vue:工具准备之WebStorm安装配置
浅入深出Vue之工具准备(一):WebStorm安装配置 工欲善其事必先利其器,让我们先做好准备工作吧 导航篇 WebStorm安装配置 所有工具的下载地址都可以在导航篇中找到,这里我们下载的是最新版 ...
- 浅入深出Vue:代码整洁之封装
深入浅出vue系列文章已经更新过半了,在入门篇中我们实践了一个小小的项目. <代码整洁之道>一书中提到过一句话: 神在细节中 这句话来自20世纪中期注明现代建筑大师 路德维希·密斯·范·德 ...
- 浅入浅出EmguCv(三)EmguCv打开指定视频
打开视频的思路跟打开图片的思路是一样的,只不过视频是由一帧帧图片组成,因此,打开视频的处理程序有一个连续的获取图片并逐帧显示的处理过程.GUI同<浅入浅出EmguCv(二)EmguCv打开指定图 ...
- 浅入浅出EmguCv(一)OpenCv与EmguCv
最近接触计算机视觉方面的东西,于是准备下手学习opencv,从官网下载windows的安装版,配置环境,一系列步骤走完后,准备按照惯例弄个HelloWord.也就是按照网上的教程,打开了那个图像处理领 ...
随机推荐
- [51Nod1850] 抽卡大赛
link $solution:$ 朴素 $dp$,暴力枚举选择 $i$ 号人的第 $j$ 张卡片,朴素 $dp$ 即可,时间复杂度 $O(n^4)$ . 考虑对于朴素 $dp$ 的优化,发现其实是一个 ...
- Jafka源码分析——网络架构
在kafka中.每个broker都是一个server.依照一般理解,server就是一个SocketServer,其不断接收用户的请求并进行处理.在Java中进行网络连接有两种方式一种为堵塞模式一种为 ...
- 移动前端不得不了解的Meta标签
http://ghmagical.com/article/page/id/PSeJR0rPd34k
- vue创建项目配置脚手架vue-cli环境出错
1.at process._tickCallback (internal/process/next_tick.js:188:7) npm ERR! message: 'request to http ...
- Python学习笔记-列表的增删改查
- Java并发——DCL问题
转自:http://www.iteye.com/topic/875420 如果你搜索网上分析dcl为什么在java中失效的原因,都会谈到编译器会做优化云云,我相信大家看到这个一定会觉得很沮丧.很无助, ...
- 04java基础——多态
1.多态 1.1多态的概念 现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态. 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变 ...
- hdu4731 Minimum palindrome (找规律)
这道题找下规律,3个字母或者以上的时候就用abcabcabc....循环即可. 一个字母时,就是aaaaa.....; 当只有2个字母时!s[1][]=a"; s[2][]="ab ...
- Centos7.5 ZABBIX4.0.3版本的编译安装
Zabbix监控的搭建理论 1. Zabbix Server会去采集监控数据,采集的监控数据会写入到SQL数据库 2. Zabbix的WEB后端采用php语言开发,所有配置信息.用 ...
- CH0805 防线 (二分值域,前缀和,特殊性质)
$ CH~0805~ $ 防线 (二分值域,前缀和,特殊性质) $ solution: $ 注意博主所给题面的输出和原题有些不同 这道题当时想了很久很久,就是想不到怎么写.果然还是太 $ vegeta ...