Redux源码

236 阅读9分钟

redux上手

Redux是一个状态管理器 image.png

redux工作流程

  1. 创建一个 Store 来存储数据

  使用 createStore 创建一个 Store 来存放应用中的 state,应用中有且仅有一个 Store

import { createStore } from "redux";

// 创建一个reducer,定义state中的修改规则
const countReducer = function(count = 0, action) {
  switch(action.type) {
    case 'ADD':
      return count + 1;
    case 'MINUS':
      return count - 1;
    default:
      return count;
  }
}


// 创建一个store来存储state
const store = createStore(countReducer);

export default store;
  1. Store 里的 Reducer 初始化 State 并定义 State修改规则

    Reducer 是一个纯函数,它接受 Action 和当前的 state 作为参数,返回一个新的 state 。在创建 Store 时,将 Reducer 当做参数传递给 createStore, 如果 state 的结构比较复杂,可以考虑将多个 Reducer 进行拆分,再使用 combineReducer 合成。

import { combineReducers, createStore } from "redux";

// reducer 纯函数
const countReducer = function (count = 0, action) {
  console.log("action", action);
  switch (action.type) {
    case "ADD":
      return count + 1;
    case "MINUS":
      return count - 1;
    default:
      return count;
  }
};

const userReducer = function (name = "焦糖瓜子", action) {
  switch (action.type) {
    case "ENGLISH":
      return `ENGLISH:${name}`;
    case "CHINESE":
      return `中文:${name}`;
    default:
      return "无名氏";
  }
};

// 可以给combineReducers中的reducer进行重命名
const store = createStore(combineReducers({userReducer, countReducer}));

export default store;

  1. 用户通过 dispatch 一个 Action 提交数据的修改   单独使用 dispatch 提交 Action 触发 state 数据修改,无法触发视图更新
import { Component } from "react";
import store from "./store";

export default class ReduxPage extends Component {
  componentDidMount() {
    // store的变化无法实时触发render进行视图渲染,需要使用subscribe监听state的变化,强制触发更新
    store.subscribe(() => {
      this.forceUpdate();
    })
  }

  handleAdd = () => {
    // 通过dispatch提交action:{ type: 'ADD' }
    store.dispatch({ type: 'ADD' });
  }

  render() {
    return (
      <div>
        ReduxPage上手界面, redux缓存的数据: {store.getState()} <br />
        <button onClick={this.handleAdd}>增加</button>
        <button onClick={this.handleMinus}>减少</button>
      </div>
    );
  }
}
  1. Store 自动调用 Reducer, 并将当前 StateAction 传给 ReducerReducer 根据传入的 Actiontype,返回新的 State
// reducer根据action提交过来的type,返回新的state
const countReducer = function (count = 0, action) {
  console.log("action", action);
  switch (action.type) {
    case "ADD":
      return count + 1;
    case "MINUS":
      return count - 1;
    default:
      return count;
  }
};

手写redux

createStore

createStore(reducer, enhancer)

  作用: 创建一个store来存放应用中的所有数据。

  参数:

  • reducer(Function): 纯函数
  • enhancer(Function):Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator

  返回值: (store)保存所有应用的state对象。store中有几个方法: getState(),dispatch(action),subscribe(listener),replaceReducer(nextReducer) 各方法的使用参考 官方文档

/**
 * 创建一个store来存放应用中的所有数据
 * @param {Function} reducer 一个纯函数
 * @returns 返回保存所有state的对象:包含 getState、dispatch、subscribe方法
 */
export default function createStore(reducer) {
  let currentState;
  let currentListeners =[]; // 用于保存所有的listener
  
  /**
   * 分发action。这是触发state变化的唯一途径
   * @returns 返回当前的state
   */
  const getState = () => {
    return currentState;
  }

  /**
   * 分发action。这是触发state变化的唯一途径
   * 使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reduce 函数
   * @param {Object} action 描述应用变化的普通函数
   * @returns 要dispatch的action
   */
  const dispatch = (action) => {
    currentState = reducer(getState(), action);
    currentListeners.map(listener => listener());
    return action;
  }

  // 执行一次dispatch, 初始化reducer的数据, 返回reducer的default数据
  dispatch({ type: 'redux/source' });

  const subscribe = (listener) => {
    if (typeof listener !== 'function') {
      throw new Error('listener必须是一个函数!');
    }
    // 将所有的监听函数存入currentListener中
    currentListeners.push(listener);
    return () => {
      currentListeners = [];
    }
  } 

  return {
    getState,
    dispatch,
    subscribe
  }
}

combineReducer

  随着应用变得越来越复杂,可以将 reducer函数 拆分成多个单独的函数(即多个reducer), 拆分后每个reducer管理 state 的一部分。

  combineReducer: 接收由多个 reducer 组合而成的对象,返回一个新的 reducer, 每个 reducer 管理state中属于自己的一部分

/**
 * 把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数
 * 然后就可以对这个 reducer 调用 createStore 方法
 * @param {Object} reducers 接收由多个reducer组合而成的对象
 * @returns 返回一个由reducer组合的reducer
 */
export default function combineReducers(reducers) {
  if (Object.prototype.toString.call(reducers) !== '[object Object]') {
    throw new Error('combineReducers要求传入一个由reducer组合而成的对象');
  }

  // 返回一个reducer: 参数state和action
  return (state = {}, action) => {
    // reducer 返回一个新的state, 最新的newState通过执行reducers中的reducer得来
    let newState = {};
    
    // 执行所有的reducer获取最新的数据,更新state
    for (let key in reducers) {
      let reducer = reducers[key];
      newState[key] = reducer(state[key], action);
    }
    return newState;
  }
}

applyMiddleware

  applyMiddleware的作用是增强 dispatch 功能,方便外部自定义功能的 middleware 来扩展

第一版 实现日志的记录

  1、要求在用户提交action时,输出日志

// loggerMiddleware.js
// 加强dispatch的同时,在函数内部要执行 dispatch的基础功能
const store = createStore(reducer);
const next = store.dispatch;

store.dispatch = action => {
    console.log(`开始执行${action.type}`);
    next(action);
    console.log(`执行完毕${action.type}`);
}

第二版 记录异常

  又有一个需求,需要记录每次数据出错的原因,我们扩展下dispatch

// errorMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;

// 在记录日志的基础上记录异常
store.dispatch = action => {
    try{
        console.log('store的state树', store.getState());
        next(action);
    } catch(e) {
        console.log(e);
    }
}

多功能中间件的合作

  如果最新需求要求: 即记录日志也记录异常,输出当前state树。需要将两个函数进行合并

// errorMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;

store.dispatch = action => {
  try {
    console.log('store的state树', store.getState());
    // 记录日志
    console.log(`开始执行${action.type}`);
    next(action);
    console.log(`执行完毕${action.type}`);
  } catch(e){
    console.log('e', e);
  }
}

  如果继续来新的需求,每次都要手动改dispatch的扩展。所以我们需要考虑下实现扩展性很强的多中间件合作模式

  1、把记录日志的 loggerMiddleware 进行提取

// loggerMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;

const loggerMiddleware = action => {
   console.log(`开始执行${action.type}`);
   next(action);
   console.log(`执行完毕${action.type}`);
}

store.dispatch = loggerMiddleware;

  2、将记录异常的 errorMiddleware 进行提取,正常流程记录store中的数据

// errorMiddleware.js
const store = createStore(reducer);
const next = store.dispatch;

const errorMiddleware = action => {
    try {
       console.log('store的state树', store.getState());
       loggerMiddleware(action);
    }
    console.log('e', e);
}
store.dispatch = errorMiddleware;

  3、目前存在的问题: errorMiddleware 里的执行函数 next 写死了 loggerMiddleware, loggerMiddleware 中的执行函数写死了 nextstore.dispatch , 都进行了强关联, 耦合性过高。我们的需求是 应该任意中间件都可以糅合使用

const store = createStore(reducer);
const next = store.dispatch

// loggerMiddleware 需要接收一个next,进行升阶操作
const loggerMiddleware = next => action => {
   console.log(`开始执行${action.type}`);
   next(action);
   console.log(`执行完毕${action.type}`);
}

const errorMiddleware = next => action => {
    try {
       console.log('store的state树', store.getState());
       next(action);
    } catch(e) {
       console.log('e', e);
    }
}

const store = createStore(reducer);
store.dispatch = errorMiddleware(loggerMiddleware(next))

  4、将中间件进行模块化划分

  不同的中间件放入不同的文件中。当前loggerMiddlewareerrorMiddleware中间件依然强关联了 store, 继续升阶,将 store 作为参数传入

// loggerMiddleware.js
// loggerMiddleware 需要再接收一个store,进行再次升阶操作
export default function loggerMiddleware = store => next => action => {
  console.log(`开始执行${action.type}`);
  next(action);
  console.log(`执行完毕${action.type}`);
}


// errorMiddleware.js
export default function errorMiddleware = store => next => action => {
    try {
       console.log('store的state树', store.getState());
       // 当前顺序 errorMiddleware中的next为loggerMiddleware
       next(action);
    } catch(e) {
       console.log('e', e);
    }
}


// store.js
const store = createStore(reducer);
const next = store.dispatch;
const newErrorMiddleWare = errorMiddleWare(store);
const newLoggerMiddleWare = loggerMiddleWare(store);

store.dispatch = newErrorMiddleWare(newLoggerMiddleWare(next));

中间件使用方式优化

  根据上节实现的中间件,但是中间件的使用方式不友好,当前需要将中间件的细节进行封装。实际上我们不需要知道中间件的具体细节,只需要知道这有中间件就可以了。我们可以将细节进行封装,通过扩展createStore来实现。 

  其中store.dispatch = errorMiddleware(loggerMiddleware(next))中间件合并方式,我们可以通过一个compose函数来实现。

// store.js
const store = createStore(reducer);
const next = store.dispatch;

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)));
}

store.dispatch = compose(errorMiddleware(store), loggerMiddleware(store))(next);

  目前,虽然使用了compose封装了中间件,但是store.dispatch等细节依然暴露在外面,我们可以继续封装升级createStore的功能。

  实现 applyMiddleware:接收中间件 返回一个存放数据的store

// applyMiddleware.js
/**
 * 加强dispatch
 * @param  {...any} middlewares 中间件
 */
export default function applyMiddleware(...middlewares) {
  /**
   *
   * @param  {...any} funcs 中间件
   * @returns 将中间件组合起来,返回一个新的函数
   */
  function compose(...funcs) {
    console.log("funcs", funcs);
    if (funcs.length === 0) {
      return (arg) => arg;
    }

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

    return funcs.reduce(
      (a, b) =>
        (...args) =>
          a(b(...args))
    );
  }

  return store => {
    // 增强store中的dispatch
    let dispatch = store.dispatch;
    // 中间件的参数
    let params = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    };

    // 将参数传入每个middleware
    let middlewaresChain = middlewares.map((middleware) => middleware(params));
    
    // 重写dispatch
    dispatch = compose(...middlewaresChain)(dispatch);
    return {
      ...store,
      dispatch,
    };
  };
}


// store.js
const store = createStore(reducer);
const stongerStore = applyMiddlewares(errorMiddleware, loggerMiddleware)(store);

  现在优化有的 applyMiddleware 依然存在问题,这将导致创建store时需要同步创建两个store,为了统一,简化使用方法, 我们可以通过修改createStoreapplyMiddleware作为参数来进行增强createStore的功能

// createStore.js
/**
 * 创建一个store来存放应用中的所有数据
 * @param {Function} reducer 一个纯函数
 * @returns 返回保存所有state的对象:包含 getState、dispatch、subscribe方法
 */
export default function createStore(reducer, enhancer) {
  let currentState;
  let currentListeners =[]; // 用于保存所有的listener
  
  if (enhancer) {
    return enhancer(createStore)(reducer);
  }

  /**
   * 分发action。这是触发state变化的唯一途径
   * @returns 返回当前的state
   */
  const getState = () => {
    return currentState;
  }

  /**
   * 分发action。这是触发state变化的唯一途径
   * 使用当前 getState() 的结果和传入的 action 以同步方式的调用 store 的 reduce 函数
   * @param {Object} action 描述应用变化的普通函数
   * @returns 要dispatch的action
   */
  const dispatch = (action) => {
    currentState = reducer(getState(), action);
    currentListeners.map(listener => listener());
    return action;
  }

  // 执行一次dispatch, 初始化reducer的数据, 返回reducer的default数据
  dispatch({ type: 'redux/source' });

  const subscribe = (listener) => {
    if (typeof listener !== 'function') {
      throw new Error('listener必须是一个函数!');
    }
    // 将所有的监听函数存入currentListener中
    currentListeners.push(listener);
    return () => {
      currentListeners = [];
    }
  } 

  return {
    getState,
    dispatch,
    subscribe
  }
}

  同时我们需要修改一些applyMiddleware的接收参数

// applyMiddleware.js
/**
 * 加强dispatch
 * @param  {...any} middlewares 中间件
 */
export default function applyMiddleware(...middlewares) {
  /**
   *
   * @param  {...any} funcs 中间件
   * @returns 将中间件组合起来,返回一个新的函数
   */
  function compose(...funcs) {
    console.log("funcs", funcs);
    if (funcs.length === 0) {
      return (arg) => arg;
    }

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

    return funcs.reduce(
      (a, b) =>
        (...args) =>
          a(b(...args))
    );
  }

  return (createStore) => (reducer) => {
    // 增强store中的dispatch
    const store = createStore(reducer);
    let dispatch = store.dispatch;
    // 中间件的参数
    let params = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    };

    // 将参数传入每个middleware
    let middlewaresChain = middlewares.map((middleware) => middleware(params));

    // 重写dispatch
    dispatch = compose(...middlewaresChain)(dispatch);
    return {
      ...store,
      dispatch,
    };
  };
}

  将applyMiddleWare的增强放入createStore中后,在创建store时与原先一样,直接只需要执行createStore生成一个即可

// store.js
const store = createStore(reducer, applyMiddleWare(errorMiddleWare, toggerMiddleWare));

bindActionCreator

  bindActionCreator 主要作用就是将 dispatchaction creator隐藏起来,把 action creator 绑定到 dispatch 上,以便外部直接调用。

  很少使用到,使用场景比较少;在react-redux中有使用

/**
 * 把一个value为不同action creator的对象,转成拥有同名key的对象。
 * 同时使用dispatch对每个action creator 进行包装,以便可以直接调用它们
 * 
 * actionCreators也可以是数组,但是对于使用不是很友好
 * @param {Function | Object} actionCreators 
 * @param {Function} dispatch 
 */

function bindActionCreator (actionCreator, dispatch) {
  // 返回通过dispatch增强的actionCreator
  // args: action creator传入的参数
  return (...args) => dispatch(actionCreator(...args));
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch);
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error('actionCreators必须为Function或者Object');
  }

  let boundActionCreators = {};
  for(let key in actionCreators) {
    let actionCreator = actionCreators[key];
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);

    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }

  return boundActionCreators;
}