You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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'
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/configuration/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
import { createStore } from 'redux'
const store = createStore(reducer, preloadedState, enhancer)
const store = createStore(reducer, enhancer)
export default store
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
return enhancer(createStore)(reducer,preloadedState)
}
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))
export default store
createStore暴露的API
在createStore.js最后,可以看到向外暴露的API:
const store = {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
return store
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
)
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(
enhancer
)}'`
)
}
return enhancer(createStore)(reducer,preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error(
`Expected the root reducer to be a function. Instead, received: '${kindOf(
reducer
)}'`
)
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
`Actions must be plain objects. Instead, the actual type was: '${kindOf(
action
)}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.`
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
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
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error(
`Expected the listener to be a function. Instead, received: '${kindOf(
listener
)}'`
)
}
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/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
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/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
// 定义监听函数 A
function linstenerA() {
console.log('A')
}
// 订阅A,并获取A的解绑函数
const unSubscribeA = store.subscribe(linstenerA)
// 定义监听函数 B
function listenerB() {
// 在 B 中解绑 A
unSubscribeA()
}
// 定义监听函数 C
function listenerC() {
}
// 定义监听函数 D
function listenerC() {
}
// 订阅 B
store.subscribe(listenerB)
// 订阅 C
store.subscribe(listenerC)
// 订阅 D
store.subscribe(listenerD)
function replaceReducer(nextReducer){
if (typeof nextReducer !== 'function') {
throw new Error(
`Expected the nextReducer to be a function. Instead, received: '${kindOf(
nextReducer
)}`
)
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE } as A)
return store
}
Redux源码分析
Redux是一个优秀的JS状态管理工具,它继承了Flux单向数据流的思想,并在此基础上强调三个基本原则:
利用Redux可以帮助我们构建状态可控的web应用,下面就让我们来了解下它是如何工作的吧!
redux项目结构
本文基于redux4.2.0版本,项目结构如下:
utils
为了便于后续的源码阅读,我们简单先过一遍Redux的工具函数。
actionTypes.js
randomString函数用来生成一个随机字符串。
ActionTypes函数向外暴露了三个预定义的action类型,供redux本身使用。
isPlainObject.js
isPlainObject函数用来判断一个对象是否是一个纯对象。
至于什么是纯对象呢?简单来说就是通过new Object()或者字面量的方式构建出来的对象
warning.js
warning函数用来输出警告信息,特别需要注意的是这里对console作了一层判断,原因是console有兼容性问题,ie8及其以下不支持。
redux的入口 — index.js
index.js是整个redux项目的入口,它的作用就是对外暴露可供开发者使用的API。
index文件有两个需要了解的地方:
导出的__DO_NOT_USE__ActionTypes
从导入的源头来看,正是utils/actionTypes.js文件中的ActionTypes对象。
根据这个变量的命名,可以知道这个变量是用来帮助开发者做类型检查的,防止使用了redux自定义的action类型。
空函数isCrushed
根据源码的注释以及变量命名,这个函数是用来检查redux代码是否被压缩。
我们知道js代码压缩后,函数名/变量名都会被简化。
这里就做了判断,如果是在开发环境下,使用了压缩过的redux代码,就会报错。
redux的核心 — createStore.js
createStore的参数
我们知道,使用redux的第一步,就是要通过createStore来创建一个store。
createStore接收三个参数,分别是reducer,preloadedState和enhancer。
但在实际使用中,我们可以只传reducer和enhancer,跳过不传preloadedState,redux是如何做到的呢?
答案是redux做了一层转换。
在createStore.js中可以找到下面这段代码:
当preloadedState的类型是function,且第三个参数是undefined时,就将preloadedState的值赋给enhancer,并将preloadedState重新设置为undefined。
另外是enhancer,中文可以翻译为增强器,它是一个函数,用来增强store。
createStore里有这么一行代码:
可以看到,如果传入了enhancer,就会调用它,并返回一个被增强过的store。
在redux中,常用的enhancer就是redux-thunk和redux-saga这些中间件,使用这些中间件需要搭配applyMiddleware使用,applyMiddleware的作用就是将这些中间件转化为能被redux调用的enhancer。
下面是一个使用redux-thunk的例子
createStore暴露的API
在createStore.js最后,可以看到向外暴露的API:
可以看到向外暴露了一个对象,对象里包括了几个方法,其中dispatch, subscribe, getState是比较常用的。
接下来正式解析createStore的源码
判断入参是否正确
这里分别做了一下几件事:
定义初始化变量
这里将入参重新赋值为变量,还声明了两个订阅者列表,供发布通知、取消订阅、订阅时使用。
另外声明了一个isDispatching作为一个锁来使用,这个锁的作用是防止在开发者reducer中非法调用dispatch。若没有这个锁,开发者如果在reducer中调用了dispatch,那么dispatch执行后又会触发reducer的执行,造成死循环。
dispatch
dispatch可以说是redux工作流的核心,通过它可以把action,reducer和store三位“主角”给串联起来。
请看dispatch相关代码:
dispatch首先进行三次判断:
接着使用一个try-finally块来执行reducer
接着获取当前的订阅者列表,一一通知订阅者做数据更新,最后返回当前的action。
getState
getState比较简单,就是返回最新的currentState,而这个currentState在每次dispatch都会被更新。
同样为了保持数据的一致,通过isDispatching来判断当前是否正在执行reducer,如果是,抛出异常,提示用户不能在reducer中调用getState。
另外需要注意的是,getState返回的是currentState的引用,也就是说,我们是可以直接对store中的state进行修改的,并不需要dispatch(不知道算不算BUG)。
但直接修改state,违背了不可变对象的原则,也不会通知订阅者,所以我们还是需要使用dispatch来更新state。
subscribe
在注册订阅者前,首先做了两次判断:
将isSubscribed设置为true,表示已订阅
然后执行ensureCanMutateNextListeners函数,确保nextListeners和currentListeners不是同一个数组
然后并将订阅者添加到nextListeners中。
最后返回一个unsubscribe函数,用于取消订阅。
unsubscribe函数逻辑也差不多:
将isSubscribed设置为false,表示已取消订阅。
然后执行ensureCanMutateNextListeners函数,确保nextListeners和currentListeners不是同一个数组
最后将订阅者从nextListeners中移除。
辅助函数ensureCanMutateNextListeners
这里重点讲一下ensureCanMutateNextListeners,这个函数在订阅和取消订阅时都有用到,用来判断当前的nextListeners和currentListeners是否是同一个数组。
如果是,则用slice方法将nextListeners设置为currentListeners的浅拷贝。
在dispatch的函数中,我们可以看到:
将listeners和currentListeners都指向nextListeners,最后通过一个for循环来通知订阅者
这里实际上使用的是nextListeners
在subscribe的函数中,我们可以看到:
订阅和取消订阅,实际上使用的也是nextListeners
订阅/取消订阅是操作nextListeners,发布通知也是操作nextListeners,那么就有个问题了,为什么要声明两个订阅者列表呢?要currentListeners有何用??
答案是:正是因为操作都是针对的nextListeners,所以我们才需要一个稳定的currentListeners
设想下面这个场景,并且设想只有nextListeners一个订阅者列表:
在这个DEMO执行完毕后,nextListeners数组的内容是 A、B、C 3个listener:
接着若调用了dispatch,则会触发下面这段逻辑。
当for循环,i=1时,通知到B,B就会执行解绑A的操作
如果说不存在currentListeners,自然也就没有ensureCanMutateNextListeners,那么就会直接修改nextListeners。
解绑完订阅者A后, for循环会继续走到i=2,这时候会发现,由于A被解绑,数组中的元素都前进了一位,也就是原本C的位置变成了D,那么就会触发通知到D,C被忽略了。
为了避免这个情况,才需要两个订阅者列表,在订阅/取消订阅时,都操作新的nextListeners,不影响正在通知订阅的listeners。
replaceReducer
replaceReducer在实际项目中使用的不多,用来替换reducer。
replaceReducer函数执行前会做一个判断:判断所传reducer是否为函数
通过条件判断之后,再将nextReducer赋值给currentReducer,并触发dispatch,更新state。
初始化state
在createStoro函数的最后,还有一段代码:
为什么要执行一次dispatch呢?
还记得在初始化变量时有这么一段代码吗
假如我们没有传入preloadedState,currentState自然就是undefined。
假如我们不初始化执行一次dispatch,那么state就拿不到reducer里的默认值,那么应用在第一次getState时,也拿不到默认值,后续dispatch的时候,也就没办法在默认值的基础上做更新了。
所以我们需要默认执行一次初始化,拿到所有reducer默认的state。
结语
createStore是Redux的核心,分析createStore代码能帮我们掌握redux最基础的工作流程,由于篇幅所限,redux其余的API分析会放在下一篇。
The text was updated successfully, but these errors were encountered: