我觉得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 object
action(普通的JavaScript对象),如果要传递一个,需要使用中间件,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让我们的action为函数。注意!! 原本的action只能是一个plain object。 以下是redux-thunk中间件的写法,必须传入store,dispatch,action后才会执行真正的函数实体。 这里用的是柯里化
,只有参数够了,才会去执行函数体。
// 稍微改动过的redux-thunkconst 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}复制代码