redux源码解析
MyPrototypeWhat opened this issue · 5 comments
redux源码解析
尽量用最白的话讲明白~
目录:
createStore.js
export default function createStore(reducer, preloadedState, enhancer)
暴露createStore
函数,返回:
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
createStore大致有三种使用:
createStore(reducer)
createStore(reducer,applyMiddleware(...middleware))
createStore(reducer,initialState,applyMiddleware(...middleware))
接下来就逐个分析一下~
变量声明和类型判断
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {...}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {...}
// 这里的 enhancer 是 applyMiddleware(...) 执行后的高阶函数
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {...}
let currentReducer = reducer //当前的reducer
let currentState = preloadedState// 拿到当前 State
let currentListeners = [] // 初始化 listeners 用于放置监听函数,用于保存快照供当前 dispatch 使用
let nextListeners = currentListeners //引用传值 指向当前 listeners,在需要修改时复制出来修改为下次快照存储数据,不影响当前订阅
let isDispatching = false// 用于标记是否正在进行 dispatch,用于控制 dispatch 依次调用不冲突
/**
* 这是currentListeners的浅拷贝,所以我们可以使用nextListeners作为调度时的临时列表。
* 这样可以防止消费者在dispatch过程中 订阅/取消订阅 调用时出现任何错误
*/
//在一段时间内始终没有新的订阅或取消订阅的情况下,nextListeners 与 currentListeners 可以共用内存
// 确保可以改变 nextListeners。没有新的listener 可以始终用同一个引用
function ensureCanMutateNextListeners() {
// 需要写入新的监听之前调用,如果是指向同一个地址的话,就把nextListeners复制出来
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
类型判断,除了排除特定的type之外,还有另外一个目的就是,满足参数灵活。
getState
获取当前的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.'
/**
* 在Reducer执行时不能调用store.getState()
* reducer已收到state作为参数。
* 从顶端reducer往下递送,而不是从店里读出。
*/
)
}
return currentState
}
subscribe
向监听列表中添加监听函数,返回值为取消监听函数
如果在调用dispatch时订阅或取消订阅,则不会对当前正在进行的dispatch产生任何影响。但是,下一个dispatch调用(无论是否嵌套)将使用订阅列表的最新快照。
function subscribe(listener) {
if (typeof listener !== 'function') {...}
if (isDispatching) {...}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) { //防止多次触发
return
}
if (isDispatching) {
throw new Error(
//在Reducer正在执行时,您不能取消对存储侦听器的订阅
'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
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1) //去除队列中相对应的listener
currentListeners = null
}
}
- 判断传入的
listener
是否是function
,否则报错 - 判断
dispatch
状态,true
则报错,在reducer中不能调用。 - 声明一个变量用来判断是否订阅,防止该订阅函数多次取消订阅
ensureCanMutateNextListeners()
,判断当前监听队列是否和临时监听队列是否相同引用,如果相同则通过slice复制出来一份,赋值给nextListeners,此举为了不混淆当前的监听队列。- 将监听函数添加到临时的监听队列——nextListeners,(下文会讲)在dispatch的时候将临时监听队列同步到当前监听队列并触发。
unsubscribe
依旧触发ensureCanMutateNextListeners
,然后找到对应的listener(在subscribe
函数时形成闭包)在数组中的index,删除。currentListeners = null
(较老的版本没有这句) 这句没太搞懂为什么这么写,有大佬明白的麻烦给说下,谢了~
dispatch
执行reducer之后获取最新的state,并且依次执行监听队列函数
function dispatch(action) {
if (!isPlainObject(action)) {...}
if (typeof action.type === 'undefined') {...}
if (isDispatching) {...}
try {
isDispatching = true
currentState = currentReducer(currentState, action) //currentReducer 传入的reducer
} finally {
isDispatching = false
}
/**
* 更新最新的监听对象,相当于:
currentListeners = nextListeners
const listeners = currentListeners
如果再次触发订阅,则会执行ensureCanMutateNextListeners(),然后再次把nextListeners复制出来,添加监听
*/
const listeners = (currentListeners = nextListeners) //在生成了currentState之后遍历当前的监听列表,逐个执行
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action //返回当前action
}
- 判断是否是一个
扁平对象
- 判断
type
是否为undefined
,是则报错 - 判断是否正在
dispatch
,是则报错 - 设置当前派发状态为
true
,通过reducer
处理之后或者 最新的state
监听:
将nextListeners
直接赋值给currentListeners
,统一当前的监听列表,然后遍历执行监听函数
replaceReducer
替换reducer
function replaceReducer(nextReducer) { //替换reducer的同时会dipatch`@@redux/REPLACE${randomString()}`
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
/**
* 此操作与ActionTypes.INIT具有相似的效果。
* 新旧rootReducer中存在的任何Reducer都将收到以前的状态。这将使用旧状态树中的任何相关数据有效地填充新状态树。
*/
dispatch({ type: ActionTypes.REPLACE }) //初始化state
}
将reducer
替换之后,dispatch
一下,更新state
。一般不会用到
observable
目前没见过有用这个的...
function observable() {
const outerSubscribe = subscribe //创建一个外部的订阅函数
return {
/**
* 最小观察者订阅方法。
* @param {object} 可用作观察者的任何对象。
* 观察者对象应该有一个`next`方法
* @returns {Subscription} 具有`unsubscribe`方法的对象,
* 该方法可用于从存储中取消订阅可观察对象,并防止观察对象进一步发送值。
*
*/
subscribe(observer) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() { //如果传入的observer对象有next函数,就执行next()并将当前state放入函数中
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState) //给当前监听的数组加上observeState函数
return { unsubscribe }
},
/**
* Redux 内部没有用到这个方法,在测试代码 redux/test/createStore.spec.js 中有出现。
* https://github.com/benlesh/symbol-observable
*/
[$$observable]() {
return this
}
}
}
这部分用到了一个包symbol-observable感兴趣的可以去看下
最后dispatch({ type: ActionTypes.INIT })
创建存储时,将dispatch 'INIT',以便每个reducer返回其初始状态。这有效地填充了初始state tree。
utils
actionTypes.js/isPlainObject.js/warning.js
actionTypes.js
代码中的注释:
这些是Redux保留的私有操作类型。
对于任何未知操作,必须返回当前状态。
如果当前状态未定义,则必须返回初始状态。
不要在代码中直接引用这些操作类型。
const randomString = () => //"2.f.g.d.o.8"
Math.random()
.toString(36) //36进制
.substring(7) //从index为7开始取
.split('')
.join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`
}
export default ActionTypes
ActionTypes有三种
- INIT:在创建store的时候,初始化state
- REPLACE:在替换reducer的时候,填充新state树
- PROBE_UNKNOWN_ACTION:在combineReducer.js中使用,随机类型探测(combineReducer.js中有讲)
isPlainObject.js
isPlainObject函数是redux自己用来判断传递给reducer的action对象是一个plain object,也就是通过字面量方式或者Object构造函数的方式生成的对象,中间没有发生其他的继承情况
export default function isPlainObject(obj) {
if (typeof obj !== 'object' || obj === null) return false
let proto = obj
while (Object.getPrototypeOf(proto) !== null) {
//普通的对象while循环结束后proto的值是:Object.prototype,通过Object.create(null)生成的对象proto的值是:null
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto
}
通过Object.getPrototypeOf(obj)
和自己的原型
对比的原因:
防止一些边界情况,例如例如跨frame
访问变量时
在一个frame
里面调用父窗口的函数:
window.parent.someFunction(["hello", "world"])
在父窗口中有someFunction
的定义:
function someFunction(arg) {
if (arg instanceof Array) {
// ... operate on the array
}
}
因为两段代码所处的javascript
执行环境是不一样的,每个frame
都有自己的执行环境,也就是说两个执行环境中的Array
构造函数都是不等的,那么if语句
的判断就为false
warning.js
用来抛出错误,没什么好说的
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 {
throw new Error(message)
} catch (e) {} // eslint-disable-line no-empty
}
applyMiddleware.js
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 此时...arg=(reducer, preloadedState)
const store = createStore(...args)
// 此时 store={dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}
let dispatch = () => {...} //防止在构建期间dispatch
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// chain=[next=>action=>....]
const chain = middlewares.map(middleware => middleware(middlewareAPI))
/**
* 给每个中间件加入middlewareAPI
* 如 chian = [middleware1, middleware2, middleware3]
*/
dispatch = compose(...chain)(store.dispatch)
/**
* 通过compose函数传入chain一层层增强dispatch
* dispatch = compose(...chain)(store.dispatch),即执行 middleware1(middleware2(middleware3(store.dispatch)))
*/
return {
...store,
dispatch //通过中间件增强之后的dipatch
}
}
}
常用方法createStore(reducer,applyMiddleware(...middleware))
执行之后返回:
(createStore)=>(...args)=>{...; return{...store,dispatch //通过中间件增强之后的dipatch}}
精简下来之后看着就清晰了。
-
createStore
就是上文createStore.js
中的createStore
,用来生成store
, -
...args
就是(reducer, preloadedState)
, -
...args
就是(reducer, preloadedState)
, -
let dispatch
函数,防止中间件在构造中dispatch
-
声明
middlewareAPI
注入中间件中,通过map
,将各个中间件
执行一遍之后返回一个数组,也是就是chain
变量 -
通过
compose
(下文讲)将dispatch
加强之后,将createStore
中生成的store
中的dispatch
替换返回。
compose.js
类似koa2洋葱圈,依次加强dispatch
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
函数式编程,整体代码很短...
源代码注释: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.(从右到左组成单参数函数。最右边的函数可以接受多个参数,因为它为生成的复合函数提供签名。)
每次循环返回一个函数 (...args) => a(b(...args))
,所以每当一次循环结束之后a=(...args) => a(b(...args))
例如[f,g,h].reduce((total,item)=>(...args) => total(item(...args)))
reduce如果没有初始值,第一位参数取数组的第一项
则:第一次循环:total=(...args) =>f(g(...args))
第二次循环:total=(...args) =>f(g(h(...args)))
bindActionCreators.js
作用是将单个或多个ActionCreator转化为dispatch(action)的函数集合形式,简化书写
bindActionCreators之后的action
boundActionCreators={
action1:function() {return dispatch(actionCreator.apply(this, arguments))},
action2:function() {return dispatch(actionCreator.apply(this, arguments))},
action3:function() {return dispatch(actionCreator.apply(this, arguments))},
}
function bindActionCreator(actionCreator, dispatch) { //此函数就是返回一个function,并且帮你dispatch
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
if (typeof actionCreators === 'function') {
/**
* 如果是function则返回一个
* function() {
return dispatch(actionCreator.apply(this, arguments))
}
actionCreator应当接收参数并返回一个action
*/
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {...}
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key] //遍历拿到dispatch函数
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
不使用bindActionCreators
时候:
function actions(dispatch) {
return {
onIncrement: () => dispatch(increment())
};
}
使用bindActionCreators
:
let actions = {
addItem: ()=>({type: types.ADD_ITEM,paload})
}
bindActionCreators(actions, dispatch);
combineReducers.js
将多个reducer合成一个,并且改变state的结构
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {...}
}
if (typeof reducers[key] === 'function') { //过滤掉不是function的
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// This is used to make sure we don't warn about the same
// 这是用来确保我们不会发出同样的警告
// keys multiple times.
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers) //遍历执行两次数组内的reducer,对返回值进行判断
} catch (e) {
shapeAssertionError = e
}
上面一系列操作,干了几件事:
- 生成
finalReducers
,{reducer函数名:reducer}
- 根据
finalReducers
拿到所有键名
生成finalReducerKeys
- 执行
assertReducerShape
assertReducerShape
遍历执行所有的reducer,进行返回类型的判断
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` +
`you can use null instead of undefined.`
/**
* 在初始化期间返回undefined。
* 如果传递给reducer的state未定义,则必须显式返回初始状态。
* 初始状态不能是未定义的。如果不想为此reducer设置值,可以使用NULL而不是undefined。`
*/
)
}
if (
typeof reducer(undefined, {
type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined'
) {
throw new Error(
`Reducer "${key}" returned undefined when probed with a random type. ` +
`Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
/**
* 使用随机类型探测时,reducer“${key}”返回undefined。
* 不要试图在“redux/*”名称空间中处理${ActionTypes.INIT}或其他操作。
* 他们被认为是私人的。相反,您必须返回任何未知操作的当前状态,除非未定义,
* 在这种情况下,无论操作类型如何,都必须返回初始状态。初始状态不能未定义,但可以为空。
*/
)
}
})
}
通过该函数对类型的判断之后,
再返回看剩下的combineReducers
函数
return function combination(state = {}, action) {//作为createStore中reducer参数
if (shapeAssertionError) {...}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false //判断state是否改变
const nextState = {}
/**
* 每次dispatch的时候就会遍历执行所有的reducer
*/
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state //改变了则返回改变之后的state,否则返回之前的state
}
combination
就是createStore
中reducer
参数,就是将所有reducer
整合到这个函数.来看看怎么实现的
- 首先判断上文的类型判断是否通过
- 执行
getUnexpectedStateShapeWarningMessage
,获取warningMessage
,如果存在warningMessage
直接报错 - 声明
hasChanged
,用来判断state是否更新 - 遍历所有的
reducer
,拿到上一次的state
,放入reducer
执行,拿到执行后的state
,如果为undefined
,报错,生成新的state
之后,和上一个state
进行浅比较,判断state
是否更改 state
改变了则返回改变之后的state
,否则返回之前的state
getUnexpectedStateShapeWarningMessage
判断是否有意料之外的state类型
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT //判断是初始化时reducer,还是之后的reducer
? 'preloadedState argument passed to createStore'//传递给createStore的preloadedState参数
: 'previous state received by the reducer'//reducer接收的先前状态
if (reducerKeys.length === 0) {//Store没有有效的reducer。确保传递给comineReducers的参数是一个值为Reducers的对象}
if (!isPlainObject(inputState)) {...}
/**
* 获取state上未被reducer处理的状态的键值unexpectedKeys,并将其存入cache值中。
*/
const unexpectedKeys = Object.keys(inputState).filter(
//如果reducer的属性中没有state,并且unexpectedKeyCache中没有对应的值
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
/**
* 检测是否为内置的replace action,因为当使用store的replaceReducer时会自动触发该内置action,
* 并将reducer替换成传入的,此时检测的reducer和原状态树必然会存在冲突,
* 所以在这种情况下检测到的unexpectedKeys并不具备参考价值,将不会针对性的返回抛错信息,反之则会返回。
*/
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
getUndefinedStateErrorMessage
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}