我觉得redux对于初学者并不是很友好,很多概念都不太好理解,会使用redux之后有必要看源码
createStore
redux的store存储了应用的状态树,要改变state只能通过dispatch()方法。
虽然文档中明确指出store只能有一个,但是我在工作的项目中store有可能不止一个,比如多页面应用。
参数
createStore有3个参数,分别是reducer、preloadedState、enhancer。
reducer。这里传入的reducer通常是通过combineReducers集成的reducerpreloadedState。初始化store时候很有必要传入一个初始化的state,一来可以给应用页面一个初始值,二来可以让自己或者别人了解整个app的state结构。enhancer。通常就是一些redux的中间件,中间件的概念有点绕。
createStore做了参数校验和类型检测,除了reducer是必须传入之外,其余的两个参数都不是必须的。
传入的形式包含以下几种。
createStore(reducer)createStore(reducer, enhancer)createStore(reducer, preloadedState)createStore(reducer, preloadedState, enhaner)
内部实现
createStore的内部包含了3个重要的变量和4个供外部调用的方法。
变量
currentState。存储了整个store管理的状态树。currentListeners。redux其实是观察者模式的一个实践。我们可以把currentListeners看做state变化后待执行的函数列表。isDispatching。是否正在进行dispatch。nextListners。当进行subscribe操作时候,先把新的listener函数push到nextListners数组中,作为一个最新listener数组快照。
方法
- getState
外部访问store内部state的唯一方法。方法直接把
currentState内部变量直接返回。
function getState() {
return currentState;
}
- subscribe
用于增加一个listener到
nextListners。每次进行dispatch方法时候,nextListenrs替换currentListeners,传入的listener会被执行。通常来说我们会把UI的渲染,作为listener传入到subscribe中,每当state变化,redux会通知UI进行render。 listener不会看到每一个所有state的变化,因为state可能会在dispatch中变化多次后,listener才被执行。subscribe方法返回一个函数闭包,作为取消订阅。
function subscribe() {
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
- dispatch
外部唯一能改变state的方法。调用reducer获得最新的state(reducer就是构建state树的函数)并执行每一个listener方法。
dispatch只能传递
plain objectaction(普通的JavaScript对象),如果要传递一个thunk,需要使用中间件,redux-thunk。
function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 把nextListeners替换currentListeners
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
- replaceReducer 更换reducer方法。很少时候会使用这个方法。
combineReducers
把多个reducer合到一个函数中。combineReducers会调用每一个reducer,并把每一个reducer返回的state合并到state树中。
参数
类型为一个object。每个键对应的必须为一个reducer函数。
const params = {
key1: reducerfunc1,
key2: reducerfunc2,
};
假设reducerfunc1和reducerfunc2返回的state形式分别为
const state1 = {
v1: '',
v2: 0,
};
const state2 = [];
那么我们的state树就是这样形式,通过getState获取到的state对象如下。
const state = {
key1: {
v1: '',
v2: 0,
},
key2: [],
}
内部实现
闭包之前的代码都是reducer检测
- 过滤那些不是function的reducer(reducers的每一个key必须对应的reducer方法)
- 查看每一个reducer方法是否有initState,default type是否返回state
代码很好理解。
export default function combineReducers(reducers) {
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// reducer调用前的state
const previousStateForKey = state[key]
// reducer调用后的state
const nextStateForKey = reducer(previousStateForKey, action)
// 更新state树快照
nextState[key] = nextStateForKey
// state是否有变动的flag。若reducer执行导致state变动,返回一个全新的state对象,所以可以直接比较对象来判断是否有改变。
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
applyMiddleware
中间件,我觉得是整个redux中比较有意思,并且稍微有点难理解的部分。
可以在官网查看中间件的文档。
可以把中间件理解为在dispatch方法的前后做一些操作。也可以类比为java的切面。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
其实这里可以理解为俄罗斯套娃,原始的store.dispatch就是套娃的最里面那个,所有的中间件按照数组的顺序,一个把一个套住。而这个套娃的过程由compose完成。
要理解这部分,我们把中间件redux-thunk的源码也拿过来看看。
redux-thunk让我们的action为函数。注意!! 原本的action只能是一个plain object。
以下是redux-thunk中间件的写法,必须传入store,dispatch,action后才会执行真正的函数实体。
这里用的是柯里化,只有参数够了,才会去执行函数体。
// 稍微改动过的redux-thunk
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
export default thunk;
我这里一开始看不懂,为什么一开始dispatch赋值为一个空函数? 其实compose完成之后会返回一个新的dispatch,这个dispatch会替换掉那个空函数
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
最后返回store变量。
return {
...store,
dispatch
}