不一样的redux源码解读

39次阅读

共计 8209 个字符,预计需要花费 21 分钟才能阅读完成。

1、本文不涉及 redux 的使用方法,因此可能更适合使用过 redux 的同学阅读 2、当前 redux 版本为 4.0.1
Redux 作为大型 React 应用状态管理最常用的工具。虽然在平时的工作中很多次的用到了它,但是一直没有对其原理进行研究。最近看了一下源码,下面是我自己的一些简单认识,如有疑问欢迎交流。

1.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 了

2、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 做了什么
export default function combineReducers(reducers) {
// 第一次筛选,参数 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

3.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 包裹起来,这样我们就可以直接调用他们了。这个在平常开发中不常用。

4.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 源码的主要部分讲完了,如有感兴趣的同学可以去看一下我没讲到的一些东西转送门 redux;

正文完
 0