跟我一起来学习Redux源码实现,冲 冲 冲 !!!

304 阅读4分钟

我爱Redux

作者:xiaoyu311
工作岗位:前端开发工程师

1 介绍Redux

  • 1.1 Redux 是 JavaScript 状态容器,提供可预测化的状态管理(官方介绍)。其实他就是一个存储简单对象的容器(自我理解)。还是官方介绍逼格比较高。
  • 1.2 Redux据说有三大原则
    • 单一数据源(整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中)。
    • State 是只读的(唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象)
    • 使用纯函数来执行修改(为了描述 action 如何改变 state tree ,你需要编写 reducers。reducers是一个纯函数)
看完介绍是不是懵逼了,你蒙我也蒙,让我们用代码说话,把懵逼统统赶跑。

2 敲起我们的代码来

2.1 新建目录结构

  • redux
    • src
      • isPlainObject.js
      • applyMiddleware.js
      • bindActionCreators.js
      • combineReducers.js
      • compose.js
      • createStore.js

2.2 isPlainObject.js(判断是否为简单对象)

  function isPlainObject(obj) {
    if (typeof obj !== 'object' || obj === null) return false;
    let proto = obj; // 拷贝对象
    while(Object.getPrototypeof(obj) !== null) {
        proto = Object.getPrototypeof(obj); // 循环深度遍历obj,直到proto指向Object.prototype
    }
    return Object.getPrototypeof(obj) === proto;// 如果为true的话,说明对象是通过字面量或者new Object()创建的对象
  }

2.3 createStore.js(仓库创建)

// redux依赖于发布订阅模式listeners必然是少不了的
 function createStore(reducer, preloadedState, enhancer) {
 
     // 把函数缓存到数组中 等待被调用
     let currentListeners = [];
     let currentState = preloadedState;
     let initailState;
     
     function getState(){
         return currentState;
     }
     
     // 注册函数 
     function subscribe(listener) {
         if (typeof listener !== 'function') {
             throw new Error('压根不是一个函数')
         }
         currentListeners.push(listener);
         
         // 返回一个 取消订阅函数
         return function unsubscribe(){
             currentListeners.filter(l => l !== listener);
         }
     }
     
     // 配发一个动作
     function dispatch(action){
         if (!isPlainObject(action)) {
             throw new Error(`${action} 压根不是一个简单对象`);
         }
         
         if (typeof action.type === undefined) {
             throw new Error(`type 应该是一个字符串啊`);
         }
         
         // 第一次reducder执行 initailState为 undefined 所以reducer函数需要给一个初始状态state
         initailState = reducer(initailState, action);
         
         currentListeners.forEach(fn => fn()); // 执行所有的 订阅 函数
         
         return action;
         
     }
     
     dispatch({type: '@@INIT'}); // 首次执行 一次给 initailState赋值(也就是你自己定义的state)
     
     return {
         dispatch,
         subscribe,
         getState
     }
 }

2.4 combineReducers.js(合并reducer 进而合并state)

  // combinReducers 是为了 合并reducers 此函数 显然还是要返回一个reducer
  function combinReducers(reducers){
      // 这是一个reduer函数哟!
      return (state = {}, action) => {
          // 拿到你定义所有reducer 对应的key
          const finalReducerKeys = Object.keys(reducers);
          // 遍历所有reducer
          finalReducerKeys.forEach(key => {
              state[key] = reducers[key](state[key], action)
              // reducers[key] 拿到对应的 reducer执行
              // 首次执行 state[key]为undefined,可是你别忘了 你自己reducer传入了初始状态 state呦!
              // state[key] 重新 给每个reducer对应的state赋值
          });
          return state;
      }
  }

2.5 bindActionCreators.js(创建action,解决自己手动dispatch问题)

  function bindActionCreators (actions, dispatch) {
      const memo = {};
      Object.keys(actions).reduc((m, key) => {
          // 获取当前 创建 action的函数
          const actionCreator = actions[key];
          // actionCreator函数名字 对应一个 能直接派发动作的 函数
          memo[key] = (...args) => dispatch(actionCreator(...args));
      }, memo);
      return memo;
  }

2.6 重点来了 applyMiddleware.js 这才是王道!!!

介绍applyMiddleware 之前 我们先来看一个简单的中间件函数

  // 三层函数
  // 第一层函数 接收一个对象作为参数 返回了一个 新函数
  // 第二层函数 接收一个next 参数继续返回一个新函数
  // 第三层函数 接收一个action 作为参数,还记得 dispatch函数吗,是不是也接收一个参数而且也是action,好巧啊
  const logger = ({ dispatch, getState }) => next => action => {
      console.log(getState()); // 打印 之前状态
      next(action);
      console.log(getState()); // 打印 之后的状态
  }

中间件函数会传到applyMiddleware方法中调用, 给中间件 传递 需要到参数

// 调用 applyMiddleware 方式为`applyMiddleware(middlewares)(createStore)(reducer)`
 function applyMiddleware(...middlewares){
     return createStore => (...args) => {
         // 调用createStore生成 store 
         const store = createStore(...args);
         let dispatch;
         
         // 重写dispatch方法
         const middlewareApi = {
             getState: store.getState,
             dispatch: (...args) => dispatch(...args)
         };
         // 把所有中间件 执行后 放到一个数组中 chain 成了[fn1, fn2, fn3];
         const chain = middlewares.map(middleware => middleware(middlewareApi));
         
         // fn3函数调用返回值 传给fn2,fn2函数调用返回值 传给fn1,fn1继续调用
         dispatch = compose(...chain)(store.dispatch);
         return {
             ...store,
             dispatch
         }
     }
 }

2.7 compose.js(中间件合并)

   function compose (...funs) {
       let i = funs.length;
       return function(...args) {
           let result = args;
           // 从右向做开始调用数组中的函数 每一个函数到返回值,作为下一个函数调用时候到参数
           while(i>=0) {
               result = funs[i--](...result);
           }
           // 也就是说 fn3函数调用 返回的就是 middlewareApi
           return result;
       }
   }

然而官方并不是这么写的(下面就是官方实现)(逼格还是相当高的)

   function compose(...funs) {
       if (funcs.length === 0) {
           return arg => arg
       }
       
       if (funcs.length === 1) {
           return funcs[0]
       }
       
       return funs.reduc((a, b) => (...args) => a(b(...args)));
   }

Redux还有一些不常用的方法,就不再去实现了,自我感觉,中间件这块,还是有点绕。毕竟大牛实现。让我们一起学习Redux源码,都成为大牛 🐂🐂🐂