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 源码的主要部分讲完了, 如有感兴趣的同学可以去看一下我没讲到的一些东西转送门 https://github.com/reduxjs/redux/tree/master/src ;
来源: https://juejin.im/post/5c866ace51882575eb6ac3e3