仅以此文记录个人阅读redux源码程度
介绍:「redux是一个代码十分短小精悍的状态管理库,中间件和函数式编程玩得很溜」
redux特点
- state是唯一的。在一个应用中,应该保持只有一个顶级state
- reducer是一个纯函数。纯函数的意思是相同的输出只会得到相同的输出,一个非纯函数举例:
function random() { return Math.random() } - state只读,修改state只能通过dispatch去派发一个action,然后reducers进行匹配直接修改state让时间旅行数据回溯会变得困难起来
目录结构
目录十分的简单清晰明了,核心代码在
createStore.jsapplyMiddleware.jscombindReducers.jscompose.js
其它几个简单介绍下:bindActionCreators.js,绑定一系列actions,其实就是简化以下action操作(我没怎么用过...),utils目录下是一些工具类,像dispatch时传的数据,是否是一个纯对象,抛出一个警告等
createStore
「构造store」
- 接收三个参数,reducer,preloadState,enhancer,这三个参数的意思分别是reducer就是一个纯函数,会传入当前state和一个action得到一个全新的state,preloadState,是一个类似__INITIAL_STATE__的东西,默认state的意思,enhancer,增强型中间件(骚操作,强悍就是这里做的)
- 对入参进行控制,因为preloadState可以是空的,enhancer也可以是空的,所以会存在
createStore(reducer,applyMiddleware(a, b)),这种情况(个人疑问🤔,为啥不做成一个对象createStore({ reducer, preloadState, enhancer }))这样不是美滋滋了么),如果有传enhancer,就enhancer(createStore)(reducer, preloadedState),先执行enhancer,因为enhancer最后返回了两层函数(这是不是叫柯里化),个人疑问,跟简化成enhancer(createStore,reducer preloadedState)区别是什么 - 定义一些变量
let currentReducer = reducer // 当前reducer
let currentState = preloadedState // 前置的state
let currentListeners = [] // 当前事件监听器
let nextListeners = currentListeners // 下一次事件监听器,这里主要是订阅和取消订阅会有坑,下面会说到
let isDispatching = false // 是否在分发事件中
- 定义一个方法:ensureCanMutateNextListeners(确保可以突变nextListeners),浅复制currentListeners器给nextListeners
- 定义getState方法,先判断如果是isDispatching为true,阻止获得state,其实就是防止你在reducer中直接获取state(为啥)
- 定义subscribe,接收一个参数
listener,先判断listener是否是一个函数和isDispatching是否为true,分别是防止传进来的不是一个函数和防止在reducer中订阅(因为diapatch会触发reducer,reducer中又触发dispatch,最终会爆栈),设置一个let isSubscribed = true,表明有事件订阅中,调用ensureCanMutateNextListeners(),并且nextListeners.push(listener),最后返回一个取消订阅函数,取消订阅函数中判断如果isSubscribed === false不执行,防止多次触发,如果是isDispatching也是不允许取消订阅,调用ensureCanMutateNextListeners(),删除掉nextListeners中的对应的listener - 定义dispatch函数,判断action必须是一个纯对象并且要有type字段,而且reducer中禁止派发actions,isDispatching设置true,
currentState = currentReducer(currentState, action),调用currentReducer,传入当前state和action,得到新的state,最后currentListeners = nextListeners,回调所有的listeners,这就是需要nextListeners的原因,因为redux是用for循环遍历所有事件,如果此时有一个listener被干掉了,索引会变,这样会导致某个事件监听会丢失,所以要保存一份快照。 - 定义replaceReducer,替换reducer,没用过,不知道使用场景
- observable,没有机会使用,暂时不去仔细看
- 最后调用
dispatch({ type: ActionTypes.INIT }),初始化state,返回
return {
dispatch,
subscribe,
getState,
replaceReducer,
[?observable]: observable
}
applyMiddleware
「个人认为是精华部分」 完整代码如下
function applyMiddleware(middlewares) {
return (createStore) => (...args) => {
// 此时得到一个正常的store
const store = createStore(...args);
// 此处是防止中间件提前使用了dispatch
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: (action) => dispatch(action)
}
// 下面会返回三层函数,我就是在这里被绕晕了
// 先执行一次中间件
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 调用compose(compose下面讲)去顺序合成中间件,此处的dispatch就是最原始的dispatch
dispatch = compose(...chain)(store.dispatch);
//最后把这个增强型的dispatch返回出去
return {
...store,
dispatch
};
};
}
}
所以很容易看得出来,中间件的传参和函数体,必须要满足一定格式,否则会有问题,看看「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;
可以看到,「redux-thunk」的源码就这几行,拿着这个去跟上面源码去走一遍,开始吧.
- 引入这个thunk
applyMiddleware([
reduxThunk
])
- 此时这个thunk实际如下
({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }return next(action);
};
- middleware会执行一次map,这个map又被执行了一遍,现在这个如下
(next) => (action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }return next(action);
};
- 最后,通过compose去组合这些中间件,因为此处只有一个,所以,最后得到
(action) => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); }return next(action);
};
现在结合redux-thunk官方的示例看下,是不是理解了
// 一个函数,返回了一个匿名函数
function incrementAsync() {
return (dispatch) => {
setTimeout(() => {
// Yay! Can invoke sync or async actions with `dispatch`
dispatch(increment());
}, 1000);
};
}
//在匿名函数中调用dispatch
store.dispatch(incrementAsync())
增强型dispatch最后分解出来是这样子的thunk1(thunk2(thunk3(store.dispatch))),按照顺序串行下去,1的next就是2,2的next就是3,一直往下,最后得到一个最终的next,那个就是store.dispatch(最原始的dispatch)
compose
简单一句话就是组合函数的执行
function compose (...func) { if (func.length === 0) { return args => {}; }if (func.length === 1) { return func[0] } // 不断组合函数,最终得到例如这样的函数 return func.reduce((a, b) => (...args) => a(b(...args)))
}
最后在applyMiddle中的compose返回值就得到(store.dispatch) => thunk1(thunk2(thunk3(store.dispatch)))
combindReducers 组合多个reducer
为什么叫reducer,因为这种形式跟Array.prototype.reduce(initValue, current)这种用当前值去跟将要改变的值作一个更改得到一个新值的形式是一样的,是一个不断累加的过程
- 接收reducers对象形如:
{ reducer1: Function, reducer2: Function }const defaultState = { count: 0, run: 'get Run' };
const reducer1 = function(state = defaultState, action) { if (action.type === 'add') { return state.count++; }
if (action.type === 'getValue') { return state.count; }
if (action.type === 'run') { return state.run; } };
- 过滤掉无效的reducer,即判断如果reducerx的值非函数,直接剔除,此处的做法是用finalReducers,去过滤,有效的值才会存放到里面
- 返回一个叫combination的方法,接收state和action参数
- 定义hasChanged变量(用于标记state是否改变),nextState,新的state
- for循环遍历山第二步得到的finalReducer,取得以下几个变量
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 这里应该是preloadState如果传了值才有用
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
- 判断一下得到的nextStateForKey是否存在,不存在会抛出异常,存在即往
nextState[key]存入这个值 - 最后判断
nextState[key]!==state[key],如果不等于hasChanged会置为true - 循环完成后,再一次判断
hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length
- 最后根据hasChanged是否为true,决定返回nextState还是现有的state
从上面的做法,个人感觉有个不太好的缺点,就是每个reducer的更改,要全部遍历现有的reducer,感觉浪费资源
以上就是个人对于redux全部的源码分析了
本文使用 mdnice 排版