共计 8083 个字符,预计需要花费 21 分钟才能阅读完成。
Redux 作为大型 React 应用状态管理最常用的工具。它是一个应用数据流框架,与 Flux 框架类似。它是零依赖的,可以配合其他框架或者类库一起使用。虽然在平时的工作中很多次的用到了它,但是一直没有对其原理进行研究。最近看了一下源码,下面是我自己的一些简单认识。
- createStore
结合使用场景我们首先来看一下 createStore 方法。
// 这是我们平常使用时创建 store | |
const store = createStore(reducers, state, enhance); |
以下源码为去除异常校验后的源码,
export default function createStore(reducer, preloadedState, enhancer) { | |
// 如果有传入合法的 enhance,则通过 enhancer 再调用一次 createStore | |
if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.') | |
} | |
return enhancer(createStore)(reducer, preloadedState) // 这里涉及到中间件,后面介绍 applyMiddleware 时在具体介绍 | |
} | |
let currentReducer = reducer // 把 reducer 赋值给 currentReducer | |
let currentState = preloadedState // 把 preloadedState 赋值给 currentState | |
let currentListeners = [] // 初始化监听函数列表 | |
let nextListeners = currentListeners // 监听列表的一个引用 | |
let isDispatching = false // 是否正在 dispatch | |
function ensureCanMutateNextListeners() {} | |
function getState() {} | |
function subscribe(listener) {} | |
function dispatch(action) {} | |
function replaceReducer(nextReducer) {} | |
// 在 creatorStore 内部没有看到此方法的调用,就不讲了 | |
function observable() {} | |
// 初始化 store 里的 state tree | |
dispatch({type: ActionTypes.INIT}) | |
return { | |
dispatch, | |
subscribe, | |
getState, | |
replaceReducer, | |
[$$observable]: observable | |
} | |
} |
我们可以看到 creatorStore 方法除了返回我们常用的方法外,还做了一次初始化过程 dispatch({type: ActionTypes.INIT}); 那么 dispatch 干了什么事情呢?
/** | |
* dispath action。这是触发 state 变化的惟一途径。* @param {Object} 一个普通 (plain) 的对象,对象当中必须有 type 属性 | |
* @returns {Object} 返回 dispatch 的 action | |
*/ | |
function dispatch(action) { | |
// 判断 dispahch 正在运行,Reducer 在处理的时候又要执行 dispatch | |
if (isDispatching) {throw new Error('Reducers may not dispatch actions.') | |
} | |
try { | |
// 标记 dispatch 正在运行 | |
isDispatching = true | |
// 执行当前 Reducer 函数返回新的 state | |
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 | |
} |
这里 dispatch 主要做了二件事情
- 通过 reducer 更新 state
- 执行所有的监听函数,通知状态的变更
那么 reducer 是怎么改变 state 的呢?这就涉及到下面的 combineReducers 了。
- combineReducers
回到上面创建 store 的参数 reducers,
// 两个 reducer | |
const todos = (state = INIT.todos, action) => {// ....}; | |
const filterStatus = (state = INIT.filterStatus, action) => {// ...}; | |
const reducers = combineReducers({ | |
todos, | |
filterStatus | |
}); | |
// 这是我们平常使用时创建 store | |
const store = createStore(reducers, state, enhance); |
下面我们来看 combineReducers 做了什么
// 第一次筛选,参数 reducers 为 Object | |
// 筛选掉 reducers 中不是 function 的键值对 | |
const reducerKeys = Object.keys(reducers) | |
const finalReducers = {} | |
for (let i = 0; i < reducerKeys.length; i++) {const key = reducerKeys[i] | |
if (typeof reducers[key] === 'function') {finalReducers[key] = reducers[key] | |
} | |
} | |
const finalReducerKeys = Object.keys(finalReducers) | |
// 二次筛选,判断 reducer 中传入的值是否合法(!== undefined)// 获取筛选完之后的所有 key | |
let shapeAssertionError | |
try {assertReducerShape(finalReducers) | |
} catch (e) {shapeAssertionError = e} | |
return function combination(state = {}, action) { | |
let hasChanged = false | |
const nextState = {} | |
// 遍历所有的 key 和 reducer,分别将 reducer 对应的 key 所代表的 state,代入到 reducer 中进行函数调用 | |
for (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i] | |
const reducer = finalReducers[key] | |
// 这里就是 reducer function 的名称和要和 state 同名的原因,传说中的黑魔法 | |
const previousStateForKey = state[key] | |
const nextStateForKey = reducer(previousStateForKey, action) | |
if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action) | |
throw new Error(errorMessage) | |
} | |
// 将 reducer 返回的值填入 nextState | |
nextState[key] = nextStateForKey | |
hasChanged = hasChanged || nextStateForKey !== previousStateForKey | |
} | |
// 发生改变了返回新的 nextState,否则返回原先的 state | |
return hasChanged ? nextState : state | |
} | |
} |
- 这里 reducer(previousStateForKey, action)执行的就是我们上面定义的 todos 和 filterStatus 方法。通过这二个 reducer 改变 state 值。
- 以前我一直很奇怪我们的 reducer 里的 state 是怎么做到取当前 reducer 对应的数据。看到 const previousStateForKey = state[key]这里我就明白了。
- 这里还有一个疑问点就是 combineReducers 的嵌套,最开始也我不明白,看了源码才知道 combineReducers()=> combination(state = {}, action),这里 combineReducers 返回的 combination 也是接受 (state = {}, action) 也就是一个 reducer 所以可以正常嵌套。
看到这初始化流程已经走完了。这个过程我们认识了 dispatch 和 combineReducers; 接下来我们来看一下我们自己要怎么更新数据。
用户更新数据时,是通过 createStore 后暴露出来的 dispatch 方法来触发的。dispatch 方法,是 store 对象提供的更改 currentState 这个闭包变量的唯一建议途径(注意这里是唯一建议途径,不是唯一途径,因为通过 getState 获取到的是 state 的引用,所以是可以直接修改的。但是这样就不能更新视图了)。
正常情况下我们只需要像下面这样
//action creator | |
var addTodo = function(text){ | |
return { | |
type: 'add_todo', | |
text: text | |
}; | |
}; | |
function TodoReducer(state = [], action){switch (action.type) { | |
case 'add_todo': | |
return state.concat(action.text); | |
default: | |
return state; | |
} | |
}; | |
// 通过 store.dispatch(action) 来达到修改 state 的目的 | |
// 注意: 在 redux 里, 唯一能够修改 state 的方法, 就是通过 store.dispatch(action) | |
store.dispatch({type: 'add_todo', text: '读书'});// 或者下面这样 | |
// store.dispatch(addTodo('读书')); |
也就是说 dispatch 接受一个包含 type 的对象。框架为我们提供了一个创建 Action 的方法 bindActionCreators。
- bindActionCreators
下面来看下源码
// 核心代码,并通过 apply 将 this 绑定起来 | |
function bindActionCreator(actionCreator, dispatch) {return function() {return dispatch(actionCreator.apply(this, arguments)) | |
} | |
} | |
export default function bindActionCreators(actionCreators, dispatch) { | |
// 如果 actionCreators 是一个函数,则说明只有一个 actionCreator,就直接调用 bindActionCreator | |
if (typeof actionCreators === 'function') {return bindActionCreator(actionCreators, dispatch) | |
} | |
// 遍历对象,然后对每个遍历项的 actionCreator 生成函数,将函数按照原来的 key 值放到一个对象中,最后返回这个对象 | |
const keys = Object.keys(actionCreators) | |
const boundActionCreators = {} | |
for (let i = 0; i < keys.length; i++) {const key = keys[i] | |
const actionCreator = actionCreators[key] | |
if (typeof actionCreator === 'function') {boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) | |
} | |
} | |
return boundActionCreators | |
} |
bindActionCreators 的作用就是使用 dispatch 把 action creator 包裹起来,这样我们就可以直接调用他们了。这个在平常开发中不常用。
- applyMiddleware
最后我们回头来看一下之前调到的中间件,
import thunkMiddleware from 'redux-thunk'; | |
// 两个 reducer | |
const todos = (state = INIT.todos, action) => {// ....}; | |
const filterStatus = (state = INIT.filterStatus, action) => {// ...}; | |
const reducers = combineReducers({ | |
todos, | |
filterStatus | |
}); | |
// 这是我们平常使用时创建 store | |
const store = createStore(reducers, state, applyMiddleware(thunkMiddleware)); |
为了下文好理解这个放一下 redux-thunk 的源码
function createThunkMiddleware(extraArgument) {return ({ dispatch, getState}) => next => action => {if (typeof action === 'function') {return action(dispatch, getState, extraArgument); | |
} | |
return next(action); | |
}; | |
} | |
const thunk = createThunkMiddleware(); | |
thunk.withExtraArgument = createThunkMiddleware; | |
export default thunk; |
可以看出 thunk 返回了一个接受 ({dispatch, getState}) 为参数的函数
下面我们来看一下 applyMiddleware 源码分析
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) | |
} | |
// 每个 middleware 都以 middlewareAPI 作为参数进行注入,返回一个新的链。此时的返回值相当于调用 thunkMiddleware 返回的函数:(next) => (action) => {},接收一个 next 作为其参数 | |
const chain = middlewares.map(middleware => middleware(middlewareAPI)) | |
// 并将链代入进 compose 组成一个函数的调用链 | |
// compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/ h 都是 chain 中的函数对象。// 在目前只有 thunkMiddleware 作为 middlewares 参数的情况下,将返回 (next) => (action) => {} | |
// 之后以 store.dispatch 作为参数进行注入注意这里这里的 store.dispatch 是没有被修改的 dispatch 他被传给了 next;dispatch = compose(...chain)(store.dispatch) | |
return { | |
...store, | |
dispatch | |
} | |
} | |
} | |
// 定义一个代码组合的方法 | |
// 传入一些 function 作为参数,返回其链式调用的形态。例如,// compose(f, g, h) 最终返回 (...args) => f(g(h(...args))) | |
export default function compose(...funcs) {if (funcs.length === 0) {return arg => arg} else {const last = funcs[funcs.length - 1] | |
const rest = funcs.slice(0, -1) | |
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args)) | |
} | |
} |
也就是一个三级柯里化的函数,我们从头来分析一下这个过程
// createStore.js | |
if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.') | |
} | |
return enhancer(createStore)(reducer, preloadedState) | |
} |
也就是说,会变成这样
applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)
- applyMiddleware(thunkMiddleware)
applyMiddleware 接收 thunkMiddleware 作为参数,返回形如 (createStore) => (…args) => {} 的函数。
- applyMiddleware(thunkMiddleware)(createStore)
以 createStore 作为参数,调用上一步返回的函数(…args) => {}
- applyMiddleware(thunkMiddleware)(createStore)(reducer, preloadedState)
以(reducer, preloadedState)为参数进行调用。在这个函数内部,thunkMiddleware 被调用,其作用是监测 type 是 function 的 action。
因此,如果 dispatch 的 action 返回的是一个 function,则证明是中间件,则将 (dispatch, getState) 作为参数代入其中,进行 action 内部下一步的操作。否则的话,认为只是一个普通的 action,将通过 next(也就是 dispatch)进一步分发。
也就是说,applyMiddleware(thunkMiddleware)作为 enhance,最终起了这样的作用:
对 dispatch 调用的 action 进行检查,如果 action 在第一次调用之后返回的是 function,则将 (dispatch, getState) 作为参数注入到 action 返回的方法中,否则就正常对 action 进行分发,这样一来我们的中间件就完成了。
因此,当 action 内部需要获取 state,或者需要进行异步操作,在操作完成之后进行事件调用分发的话,我们就可以让 action 返回一个以 (dispatch, getState) 为参数的 function 而不是通常的 Object,enhance 就会对其进行检测以便正确的处理。
到此 redux 源码的主要部分学习结束。