Redux-原理解析

454 阅读8分钟

学习redux从以下几个方面入手

redux是什么为什么需要reduxRedux原理Redux使用实现一个简单的Redux

1.redux是什么

  • Redux 是一个用于 JavaScript 应用程序的状态管理库,尤其是在构建 React 应用时常用。它提供了一个中心化的方式来管理应用的状态,并确保不同的组件可以共享和更新这些状态。Redux 的设计思想来源于 Flux 架构,它通过单一数据源(Store)来管理所有状态,并且确保状态的变化是可预测的。

2.为什么需要Redux

  • 状态集中管理
  • 状态管理的可预测性
  • 方便调试
  • 跨组件共享状态
  • 状态变化的明确性
  • 解耦和可测试
  • 方便与其他库做集成(中间件功能)

3.Redux原理

3.1.Redux核心原理-store
  • store相当于一个容器用于存储你需要修改的数据
3.2.Redux核心原理-action
  • 相当于执行的一个动作,比如加+1、减-1。指定一个修改数据的动作
  • 所有的数据变化都需要用dispatch来派发一个action来进行更新数据
  • action是一个普通的JavaScript对象,用来描述这次更新的typecontent
3.3.Redux的核心理念-reducer
  • 是用来链接数据state 和动作 action
3.4.Redux的三大原则
  • 单一数据源
    • 整个应用程序的state是会被到一个Object树上,并且这个Object树只有一个store
    • 这个store上会提供 dispatchgetStatesubscribe方法
  • state只读性
    • 唯一可以修改的state的方法是触发action
  • 使用纯函数来修改
    • 通过reducer将旧 state 和 action 联系在一起, 并且返回一个新的state
    • 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分
    • 但是所有的reducer都应该是纯函数,不能产生任何的副作用

4.Redux使用

  • 安装Redux npm i redux

  • 创建一个对象, 作为我们要保存的状态

  • 创建Store来存储这个state

    • 创建store时必须创建reducer
    • 我们可以通过 store.getState 来获取当前的state
  • 通过action来修改state

    • 通过dispatch来派发action
    • 通常action中都会有type属性,也可以携带其他的数据
  • 修改reducer中的处理代码

    • 这里一定要记住,reducer是一个纯函数,不能直接修改state
    • 后面会讲到直接修改state带来的问题
  • 可以在派发action之前,监听store的变化

image.png


import { legacy_createStore as createStore } from 'redux';

let initState = {
	number: 0,
};
function reducers(state = initState, action) {
	switch (action.type) {
		case 'ADD':
			return { number: state.number + 1 };
		case 'SUB':
			return { number: state.number - 1 };
		default:
			return state;
	}
}
let store = createStore(reducers);
// 4.action
const action1 = { type: 'ADD' };
const action2 = { type: 'SUB', num: 2 };

// 5.订阅store的修改
store.subscribe(() => {
	console.log('state发生了改变: ', store.getState().number);
});

// 6.派发action
store.dispatch(action1);
store.dispatch(action2);

5.Redux 实现

5.1.实现createStroe
  • createStore.js
/**
 * 创建一个Redux store来管理和更新应用的状态
 * 
 * @param {Function} reducer 用于处理动作并更新状态的 reducer 函数
 * @param {any} preloadState 初始状态,可选参数
 * @returns {Object} 包含 dispatch、subscribe 和 getState 三个方法的 store 对象
 */
export default function createStore(reducer, preloadState) {
  // 当前状态,初始化为预加载状态
  let currentState = preloadState
  // 当前的监听器数组,用于存储订阅的监听器函数
  let currentListeners = [];
  console.log(currentState)
  /**
   * 获取当前状态
   * 
   * @returns {any} 当前的状态
   */
  function getState() {
    return currentState
  }

  /**
   * 订阅一个监听器,当状态发生变化时会自动调用
   * 
   * @param {Function} listener 监听器函数,无参数
   * @returns {Function} 用于取消订阅的函数
   */
  function subscribe(listener) {
    currentListeners.push(listener)
    // 返回一个用于取消订阅的函数
    return function unsubscribe() {
      const index = currentListeners.indexOf(listener);
      currentListeners.splice(index, 1);
    };
  }

  /**
   * 发送一个动作到 store,触发状态更新
   * 
   * @param {Object} action 动作对象,必须包含 type 属性
   * @returns {Object} 动作对象,用于链式调用
   */
  function dispatch(action) {
    // 检查动作对象是否为纯对象
    if (Object.getPrototypeOf(action) !== Object.prototype) {
      throw new Error(`动作必须是一个纯对象,如果想进行异步操作请使用中间件`);
    }
    // 检查动作对象是否包含 type 属性
    if (typeof action.type === "undefined") {
      throw new Error(`动作不能一个值为undefined的type属性`);
    }
    // 使用 reducer 函数处理动作并更新当前状态
    currentState = reducer(currentState, action);
    // 调用所有订阅的监听器函数通知状态更新
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];
      listener();
    }
  }

  // 初始化时发送一个特殊动作来设置初始状态
  dispatch({ type: '@@redux/INIT' });
  // 返回包含 dispatch、subscribe 和 getState 方法的 store 对象
  return {
    dispatch,
    subscribe,
    getState
  };
}
5.2.实现bindActionCreators
/**
 * 创建一个绑定dispatch的action创建函数
 * 这个函数的目的是将一个普通的action创建函数转换为可以直接在组件中调用的函数
 * 它通过应用apply方法传递所有的参数,并自动调用dispatch方法来分发action
 * 
 * @param {Function} action - 一个普通的action创建函数,它生成一个将被分发的action对象
 * @param {Function} dispatch - Redux store的dispatch方法,用于分发action
 * @returns {Function} - 返回一个新的函数,当调用这个新函数时,它会调用传入的action创建函数
 * 并自动将结果action对象分发到Redux store
 */
function bindActionCreator(action, dispatch) {
  return function () {
    return dispatch(action.apply(this, arguments))
  }
}

/**
 * 将action创建函数与dispatch方法绑定
 * 
 * 此函数旨在处理两种情况:
 * 1. 当传入的actions是一个函数时,直接绑定dispatch
 * 2. 当传入的actions是一个对象时,遍历其所有属性,将函数类型的action创建函数绑定dispatch
 * 
 * @param {Object|Function} actions - 一个包含多个action创建函数的对象,或单个action创建函数
 * @param {Function} dispatch - Redux的dispatch方法,用于触发state更新
 * @returns {Object|Function} - 绑定dispatch后的action创建函数或对象
 */
export default function bindActionCreators(actions, dispatch) {
  // 判断传入的actions是否为函数,若是,则直接绑定dispatch
  if (typeof actions === 'function') {
    return bindActionCreator(actions, dispatch)
  }

  // 创建一个空对象用于存储绑定后的action创建函数
  const boundActionCreators = {}

  // 遍历actions对象的所有属性
  for (const key in actions) {
    // 获取当前属性的值,即可能的action创建函数
    const actionCreator = actions[key]

    // 判断当前属性值是否为函数,若是,则将其与dispatch绑定,并存入结果对象
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }

  // 返回包含所有绑定后action创建函数的对象
  return boundActionCreators
}
5.3.实现combineReducers
/**
 * 导出一个默认的高阶reducer生成函数
 * 该函数的目的是将多个子reducer组合成一个单一的reducer函数
 * 这样可以简化reducer的管理,并确保每个子reducer都能处理特定的状态片段
 * 
 * @param {Object} reducers - 包含多个子reducer的函数对象
 * @returns {Function} - 返回一个组合了所有子reducer功能的单一reducer函数
 */
export default function combineReducers(reducers) {
  // 获取所有子reducer的键名数组
  const reducerKeys = Object.keys(reducers)

  /**
   * 返回一个组合reducer函数,它会依次调用每个子reducer来处理状态
   * 
   * @param {Object} state - 当前的应用状态对象
   * @param {Object} action - 发出的动作对象,用于描述发生的事情
   * @returns {Object} - 返回一个新的状态对象,该对象是所有子reducer处理后的结果
   */
  return function combination(state = {}, action) {
    // 初始化下一个状态对象,用于存储所有子reducer处理后的状态
    const nextState = {}

    // 遍历每个子reducer,让它们依次处理自己的状态片段
    for (let i = 0; i < reducerKeys.length; i++) {
      // 获取当前子reducer的键名
      const key = reducerKeys[i];
      // 获取当前子reducer函数
      const reducer = reducers[key];
      // 获取当前状态下的子状态
      const previousStateForKey = state[key];
      // 调用子reducer处理状态,并获取处理后的子状态
      const nextStateForKey = reducer(previousStateForKey, action);
      // 将处理后的子状态放入下一个状态对象中
      nextState[key] = nextStateForKey;
    }

    // 返回组合了所有子状态的下一个状态对象
    return nextState;
  }
}
5.4.实现applyMiddleware
import compose from "./compose";

/**
 * 导出一个默认的高阶函数applyMiddleware,用于将中间件应用到Redux store中
 * @param {...functions} middlewares - 一个或多个中间件函数
 * @returns {function} - 返回一个高阶函数,该函数接受createStore作为参数,并返回一个新的加强版的createStore函数
 */
export default function applyMiddleware(...middlewares) {
  // 返回一个高阶函数,用于包装原始的createStore函数
  return createStore => (reducer, preloadedState) => {
    // 使用原始的createStore函数创建store
    let store = createStore(reducer, preloadedState);
    // 初始化dispatch方法为store的dispatch方法
    let dispatch = store.dispatch
    // 初始化一个空数组,用于存储中间件链
    let chain = []
    // 创建一个middlewareAPI对象,提供getState和dispatch方法给中间件使用
    let middlewareAPI = {
      getState: store.getState,
      dispatch: (...action) => dispatch(...action)
    }
    // 使用map函数和middlewareAPI,将所有中间件构建成一个中间件链
    chain = middlewares.map(middleware => middleware(middlewareAPI));
    // 使用compose函数组合中间件链,并增强store的dispatch方法
    dispatch = compose(...chain)(store.dispatch);
    // 返回一个新的store对象,包括原始store的所有属性和新的dispatch方法
    return { ...store, dispatch }
  }
}
/**
 * 函数组合工具函数
 * 该函数接受多个函数作为参数,并返回一个新函数
 * 新函数的执行效果等同于依次执行每个传入的函数
 * 如果没有提供任何函数,则返回一个恒等函数
 * 如果只提供了一个函数,则直接返回该函数
 * 对于多个函数,使用reduce方法将它们组合成一个函数
 * @param {Function[]} funcs - 需要组合的函数数组
 * @returns {Function} - 组合后的函数
 */
export default function compose(...funcs) {
  // 当没有提供任何函数时,返回一个恒等函数
  if (funcs.length === 0) {
    return arg => arg
  }
  // 当只提供了一个函数时,直接返回该函数
  if (funcs.length === 1) {
    return funcs[0]
  }
  // 使用reduce方法将多个函数组合成一个函数
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}