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
createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
// 有了这一层判断,我们就可以这样传:createStore(reducer, initialState, enhancer)
// 或者这样: createStore(reducer, enhancer),其中enhancer还会是enhancer。
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== "undefined") {
if (typeof enhancer !== "function") {
throw new Error("Expected the enhancer to be a function.")
}
// enhancer的作用是扩展store,所以传入createStore来改造,
// 再传入reducer, preloadedState生成改造后的store,这一有一点递归调用的意思
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== "function") {
throw new Error("Expected the reducer to be a function.")
}
let currentReducer = reducer // 当前的reducer,还会有新的reducer
let currentState = preloadedState // 当前的state
let currentListeners = [] // 存储更新函数的数组
let nextListeners = currentListeners // 下次dispatch将会触发的更新函数数组
let isDispatching = false //类似一把锁,如果正在dispatch action,那么就做一些限制
// 这个函数的作用是判断nextListeners 和 currentListeners是否是同一个引用,是的话就拷贝一份,避免修改各自相互影响
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
// 正在执行reducer的时候,是不能获取state的,要等到reducer执行完,返回新的state才可以获取
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.")
}
// 由于dispatch函数会在reducer执行完毕后循环执行listeners数组内订阅的更新函数,所以要保证这个时候的listeners数组
// 不变,既不能添加(subscribe)更新函数也不能删除(unsubscribe)更新函数
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
ensureCanMutateNextListeners()
// 将更新函数推入到listeners数组,实现订阅
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
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 取消订阅
nextListeners.splice(index, 1)
}
}
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?"
)
}
// 正在dispatch的话不能再次dispatch,也就是说不可以同时dispatch两个action
if (isDispatching) {
throw new Error("Reducers may not dispatch actions.")
}
try {
isDispatching = true
// 获取到当前的state
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
// 循环执行当前的linstener
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// dispatch一个初始的action,作用是不命中你reducer中写的任何关于action的判断,直接返回初始的state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
// observable replaceReducer和$$observable主要面向库开发者,这里先不做解析
// replaceReducer,
// [$$observable]:
}
}
if (typeof enhancer !== "undefined") {
if (typeof enhancer !== "function") {
throw new Error("Expected the enhancer to be a function.")
}
// 把createStore传递进enhancer
return enhancer(createStore)(reducer, preloadedState)
}
前言
用了好长时间的redux,但从没有深究过原理,遇到报错更是懵逼,所以就啃了一遍它的源码,写了这篇文章,分享我对于它的理解。
API概览
看一下redux源码的index.js,看到了我们最常用的几个API:
不着急分析,我们先看一下Redux的基本用法:
这里实现的是一个点击按钮加减数字的效果,点击触发的行为,与展示在页面上的数字变化,都是通过redux进行的。我们通过这个例子来分析一下redux是怎么工作的:
store.getState()
拿到了当前的数字,初始值为0(在reducer中)目前为止,我们所有的操作都是通过store进行的,而store是通过createStore创建的,那么我们来看一下它内部的逻辑
createStore
createStore总共接收三个参数:
reducer
,preloadedState
,enhancer
,暴露给我们几个常用的API:
我们先通过接收的参数和暴露出来的api梳理一下它的机制:
首先是接收上面提到的三个参数创建一个store,store是存储应用所有状态的地方。同时暴露出三个方法,UI可以通过store.getState()获取到store中的数据,
store.subscribe(),作用是让store订阅一个更新UI的函数,将这个函数push到listeners数组中,等待执行。
store.dispatch()是更新store中数据的唯一方法,dispatch被调用后,首先会调用reducer,根据当前的state和action返回新的状态。然后循环调用listeners中的更新函数,
更新函数一般是我们UI的渲染函数,函数内部会调用store.getState()来获取数据,所以页面会更新。
看一下createStore函数的结构
结构就是这样,但是是如何串联起来的呢?下面来看一下完整的代码(删除了一些)
combineReducers
combineReducers用于将多个reducer合并为一个总的reducer,所以可以猜出来,
它最终返回的一定是一个函数,并且形式就是一般的reducer的形式,接收state和action,
返回状态:
来看一下核心代码:
applyMiddleware
redux原本的dispatch方法只能接受一个对象作为action
这么直接干脆的操作固然好,可以让每一步的操作可追踪,方便定位问题,但是带来一个坏处,比如,页面需要发请求获取数据,并且把数据放到action里面,
最终通过reducer的处理,放到store中。这时,如何做呢?
重点在于dispatch(
action
) -> middleware(action
) 这个操作,这里的action
可以是一个函数,在函数内我们就可以进行很多操作,包括调用API,然后在调用API成功后,再dispatch真正的action。想要这么做,那就是需要扩展redux(改造dispatch方法),也就是使用增强器:enhancer:
applyMiddleware(thunk)
就相当于一个enhancer,它负责扩展redux,说白了就是扩展store的dispatch方法。既然要改造store,那么就得把store作为参数传递进这个enhancer中,再吐出一个改造好的store。吐出来的这个store的dispatch方法,是enhancer改造store的最终实现目标。
回顾一下createStore中的这部分:
看下上边的代码,首先判断enhancer,也就是createStore的第三个参数不为undefined且为函数的时候,那么去执行这个enhancer。
我们看到enhancer(createStore),是把createStore传入,进行改造,先不管这个函数返回啥,我们先看它执行完之后还需要的参数
(reducer, preloadedState)
, 是不是有点眼熟呢?回想一下createStore的调用方法,createStore(reducer, state)。由此可知enhancer(createStore)返回的是一个新的createStore,而这个createStore是被改造过后的,它内部的dispatch方法已经不是原来的了。至此,达到了改造store的效果。
那到底是如何改造的呢? 先不着急,我们不妨先看一个现成的中间件redux-thunk。要了解redux中间件的机制,必须要理解中间件是怎么运行的。
我们先来看用不用它有什么区别:
一般情况下,dispatch的action是一个纯对象
使用了thunk之后,action可以是函数的形式
一般情况下,dispatch一个函数会直接报错的,因为createStore中的dispatch方法内部判断了action的类型。redux-thunk帮我们做的事就是改造dispatch,让它可以dispatch一个函数。
看一下redux-thunk的核心代码:
这里的三个箭头函数是函数的柯里化。
真正调用的时候,理论上是这样thunk({ dispatch, getState })(next)(action)。
其中,thunk({ dispatch, getState})(next)这部分,看它执行时接收的参数是一个action,那么它必然是一个dispatch方法,在此处相当于改造过后的dispatch,而这部分会在applyMiddleware中去调用,(下边会讲到)
然后从左往右看,{ dispatch, getState }是当前store的dispatch和getState方法,是最原始的,便于在经过中间件处理之后,可以拿到最原始的dispatch去派发真正的action。
next则是被当前中间件改造之前的dispatch。注意这个next,他与前边的dispatch并不一样,next是被thunk改造之前的dispatch,也就是说有可能是最原始的dispatch,也有可能是被其他中间件改造过的dispatch。
为了更好理解,还是翻译成普通函数嵌套加注释吧
总结一下:说白了,redux-thunk的作用就是判断action是不是一个函数,是就去执行它,不是就用那个可能被别的中间件改造过的,也可能是最原始的dispatch(next)去派发这个action。
那么接下来看一下applyMiddleware的源码:
先看最简单的情况:假设我们只使用了一个middleware(redux-thunk),就可以暂时抛开compose,那么这里的逻辑就相当于
dispatch = thunk(middlewareAPI)(store.dispatch)
是不是有点熟悉? 在redux-thunk源码中我们分析过:
所以,这里就将store的dispatch方法改造完成了,最后用改造好的dispatch覆盖原来store中的dispatch。
来总结一下,
理解中间件的原理是理解applyMiddleware机制的前提
另外说一下,关于redux-thunk的一个参数:
extraArgument
这个参数不是特别重要的,一般是传入一个实例,然后在我们需要在真正dispatch的时候需要这个参数的时候可以获取到,比如传入一个axios 的Instance,那么在请求时候就可以直接用这个instance去请求了总结
到这里,redux几个比较核心的概念就讲解完了,不得不说写的真简洁,函数之间的依赖关系让我一度十分懵逼,要理解它还是要用源码来跑一遍例子,一遍一遍地看。
总结一下redux就是创建一个store来管理所有状态,触发action来改变store。关于redux的使用场景是非常灵活的,可以结合各种库去用,我用惯了react,用的时候还要配合react-redux。
The text was updated successfully, but these errors were encountered: