Redux 源码及其中间件解析

354 阅读5分钟

1631499552.jpg

本次学习redux源码主要是通过示例图的形式,以便于读者更好的理解。

图上有,张三、李四、王五 三个人,张三想要修改仓库的状态进行颜色的的更改,李四和王五,监听仓库中的状态是否发生变化,如果状态发生了改变我要就要通知到这两个人(发布订阅)。

redux 的工作流

张三通过 dispatch({type:'changeColor,payload:'red'}) 派发一个 action(纯对象)给我们的reducer(纯函数)reducer 根据 老状态 返回 新状态,之后去通知 李四王五两个人(因为他们订阅了subscribe了状态的变化,所以会通知它们,至于他们进行什么操作,就不用管了)

纯函数 和 纯对象 的解释

纯函数:

  • 没有副作用(一个函数在执行的过程中产生了外部可以观察到的变化)
1.基于 `DOM API`修改页面
2.修改全局变量、基于`AJAX`发送接口
...
  • 引用透明(函数的返回值依赖于函数的入参)

纯对象

  • 通过 new Object() 或者 字面量 形式创建的对象,那源码里是如何验证的呢,其实就是基于 原型链的查找机制
/**
 * @param {any} obj The object to inspect.
 * @returns {boolean} True if the argument appears to be a plain object.
 */
export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

redux 源码的目录结构

src

  • utils (tools 工具方法库)
  • applyMiddleware.js (中间件)
  • bindActionCreators.js(绑定多个action的创建者)
  • combineReducers.js (合并多个reducer)
  • compose.js (组合函数)
  • createStore.js (创建的仓库方法)

正在更新中 ...

createStore
/**
* 创建仓库的方法,返回一个仓库,仓库就是一个JS对象
* @param {*} reducer 根据老状态,和动作计算下一个新状态
*/
const createStore = (reducer, preloadedState, enhancer) => {
 if (typeof enhancer !== 'undefined') {
   applyMiddleware(thunk, promise, logger)(createStore)
   (combinedReducer,initialState);
   return enhancer(createStore)(reducer, preloadedState);
 }
 let state = preloadedState;//可以存放任意的内容
 let listeners = [];
 function getState() {
   return state;
 }
 // dispatch 派发把订阅好的东西,全都执行,进而更新store 重新
 function dispatch(action) {
   state = reducer(state, action);
   listeners.forEach(l => l()); 
   return action;
 }
 // 发布订阅模式
 function subscribe(listener) {
   listeners.push(listener);
   return () => {
     listeners = listeners.filter(l => l !== listener);
   }
 }
 // 默认走一次派发流程
 dispatch({ type: '@@REDUX/INIT' });
 return {
   getState,
   dispatch,
   subscribe
 }
}
export default createStore;
bindActionCreators

函数 bindActionCreator 的作用在于绑定 action 与 dispatch 这样就无须单独引入 dispatch 并手动调用 dispatch({type:'ADD'}),其在项目开发中的好处不言而喻:例如对于 react 展示型组件,内部则无须引入 dispatch ,取而代之的是由父组件传递而来的经过 bindActionCreators 包装过后的函数,使其充分解耦。

/*  
    actionCreators = {
      add(){ 
         return {type:'ADD'}
      },
      minus(){
         return {type:'MINUS'} 
      }
    }
    
    or
    
    actionCreatorFirst = add(){
     return {type:'ADD'}
    }
    actionCreatorSecond = add(){
     return {type:'ADD'}
    }
    正因为可以写成这两种方式,所以才会有以下判断 `actionCreators` 是否是一个函数。
*/ 
function bindActionCreator(actionCreator, dispatch) {
    return function (...args) {
        return dispatch(actionCreator.apply(this, args));
    }
}
export default function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch);
    }
    const boundActionCreators = {};
    for (const key in actionCreators) {
        const actionCreator = actionCreators[key];
        if (typeof actionCreator === 'function') {
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
        }
    }
    return boundActionCreators;
}

combineReducers
   `redux` 规定只能有一个 reducer,只有一个仓库
   在真实项目,我们不会把所有组件的业务逻辑都写到一个reducer里面,那怎么办呢
   redux 开发者,早就给我们想到这一步了,以对象键值对的形式,对 reducer 进行一个合并
   function combineReducers(reducers) {
       return function (state = {}, action) {
           let nextState = {};
           for (let key in reducers) {
               nextState[key] = reducers[key](state[key], action);
           }
           return nextState;
       }
   }
   export default combineReducers;
##### compose
```js
/** 
 * 示例
 * compose的作用就是组合函数,将函数串联起来执行,前一个函数的输出值是后一个函数的输入值
 * function A(str) {
 *  return str + '界'
 *  }
 *  function B(str) {
 *      return str + '世'
 *  }
 *  function C() {
 *      return '你好'
 *  }
 *   // 示例 1
 *  let c = C();
 *  console.log('c',c)
 * let b = B(c)
 *  console.log('b',b)
 *  let a = A(b)
 *  console.log('a',a)
 *  // 示例 2
 *  let res = compose(A, B, C); // =>  A(B(C))
 *  console.log(res(),'compose')
 * ------------------------------------------ 打印结果
 * c 你好
 * b 你好世
 * a 你好世界 
 * 你好世界 compose
*/

export default function compose(...funcs) {
 if (funcs.length === 0) {
   return (arg) => arg
 }

 if (funcs.length === 1) {
   return funcs[0]
 }

 return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
redux-thunk
function createThunkMiddleware(extraArgument) {
 return ({ dispatch, getState }) => next => action => {
   可以像我们的 redux 派发方法,就加了这一行来判断是不是个 function
   if (typeof action === 'function') {
     return action(dispatch, getState, extraArgument);
   }

   return next(action);
 };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
//=> 在这的时候,其实我就产生疑问了,你自己执行一下干啥,我们想一下,不自己执行一下行不行
我觉得,当然可以,但是它为啥自己还执行啊,其实就是=> 柯理化的编程思想,多个参数的传入,转化为
N个函数。
export default thunk;
redux-logger
日志中间件
function logger({ getState, dispatch }) {
   return function (next) {
       return function (action) {
           console.log(store.getState())
           next(action)
           console.log(store.getState())
       }
   }
}
applyMiddleware
import compose from './compose'
//applyMiddleware(thunk, promise, logger)(createStore)
export default function applyMiddleware(...middlewares) {
 return (createStore) => (...args) => {
   const store = createStore(...args)
   // 这个错误的dispatch方法,其实就是为了,拿到改造后的 store.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: (...args) => dispatch(...args),
   }
   const chain = middlewares.map((middleware) => middleware(middlewareAPI))
   dispatch = compose(...chain)(store.dispatch)
   //=> chain 和 dispatch 这两行代码尤为重要,深切阐释了,洋葱模型的实现原理
   以此我们可以得出结论:next=>store.dispatch,他返回的那个函数就是我们改造后
   的store.dispatch,=>中间件是什么=> console.log(中间件)=>是一个函数,其实
   就是用来处理副作用的,因为reducer规定是纯函数,我们只能用 redux 中间件来处理
   副作用
   
   总结: 中间件是一个函数,在用户dispatch派发一个action到我们reducer纯函数之前
   加上了一些自己的业务逻辑,实现原理=>AOP面向切片编程

   return {
     ...store,
     dispatch,
   }
 }