4-3 React 状态管理

28 阅读6分钟

状态管理:本质对一个[全局唯一、具有响应式]变量的管理

因为是全局的,那为了流转/使用上的不混乱/冲突等,所以会对其制定流转规则,让变化变得可预测。

Redux 基本原理(手撸简版)

createStore

/**
 * 创建 Redux store
 *
 * @param reducers 用于处理 action 的 reducer 函数
 * @param initialValue 初始状态值
 * @returns 返回一个包含 getState、dispatch 和 subscribe 方法的对象
 */
export function createStore(reducers, initialValue) {
  // 初始化状态为初始值
  let state = initialValue;

  // 存储监听器的数组
  const listeners = [];

  // 获取当前状态的方法
  const getState = () => state;

  // 订阅监听器的函数
  const subscribe = (fn) => {
    // 将监听器添加到监听器数组中
    listeners.push(fn);
  };

  // 派发动作的函数
  const dispatch = (action) => {
    // 使用reducers函数计算出新的状态
    const nextState = reducers(state, action);

    // 更新状态为新的状态
    state = nextState;

    // 遍历监听器数组,并调用每个监听器函数
    listeners.forEach((fn) => fn());
  };

  // 返回一个包含getState、dispatch和subscribe的对象
  return { getState, dispatch, subscribe };
}

combineReduces

/**
 * 组合多个 reducer 函数
 *
 * @param reducers 一个包含多个 reducer 函数的对象
 * @returns 返回一个 reducer 函数,将多个 reducer 函数组合在一起
 */
export function combineReducers(reducers) {
  // 获取所有reducer的键名
  const keys = Object.keys(reducers);

  // 返回一个新的reducer函数
  return (state, action) => {
    // 创建一个新的状态对象
    const nextState = {};

    // 遍历所有reducer的键名
    keys.forEach((key) => {
      // 获取对应键名的reducer函数
      const reducer = reducers[key];

      // 获取当前状态中对应键名的值
      const prve = state[key];

      // 调用reducer函数,传入当前值和action,得到下一个状态的值
      const next = reducer(prve, action);

      // 将下一个状态的值添加到新的状态对象中
      nextState[key] = next;
    });

    // 返回新的状态对象
    return nextState;
  };
}

connect

/**
 * 连接函数,用于将组件与 Redux store 连接起来
 *
 * @param mapStateToProps 将 Redux store 中的 state 映射到组件的 props
 * @param mapDispatchToProps 将 Redux store 中的 dispatch 方法映射到组件的 props
 * @returns 返回一个高阶组件,该高阶组件将传入的组件与 Redux store 连接起来
 */
export function connect(mapStateToProps, mapDispatchToProps) {
  // 返回一个函数,该函数接收一个组件作为参数
  return function (Component) {
    // 返回一个函数,该函数接收props作为参数
    return function (props) {
      // 使用useContext钩子获取Redux的store
      const store = useContext(ReduxContext);
      // 使用useState钩子创建一个状态变量,并初始化为false
      const [, setBool] = useState(false);

      // 定义一个forceUpdate函数,用于强制更新组件
      const forceUpdate = () => setBool((val) => !val);

      // 在组件挂载后,将forceUpdate函数作为监听器订阅store的更新
      useEffect(() => store.subscribe(forceUpdate), []);

      // 返回一个JSX元素,该元素渲染传入的组件,并传入props和绑定的state和dispatch
      return (
        <Component
          {...props}
          {...mapStateToProps(store.getState())}
          {...mapDispatchToProps(store.dispatch)}
        />
      );
    };
  };
}

完整手写(可运行的)

src/store/redux.js

import { useEffect } from "react";
import { createContext, useContext, useState } from "react";

/**
 * 创建 Redux store
 *
 * @param reducers 用于处理 action 的 reducer 函数
 * @param initialValue 初始状态值
 * @returns 返回一个包含 getState、dispatch 和 subscribe 方法的对象
 */
export function createStore(reducers, initialValue) {
  // 初始化状态为初始值
  let state = initialValue;

  // 存储监听器的数组
  const listeners = [];

  // 获取当前状态的方法
  const getState = () => state;

  // 订阅监听器的函数
  const subscribe = (fn) => {
    // 将监听器添加到监听器数组中
    listeners.push(fn);
  };

  // 派发动作的函数
  const dispatch = (action) => {
    // 使用reducers函数计算出新的状态
    const nextState = reducers(state, action);

    // 更新状态为新的状态
    state = nextState;

    // 遍历监听器数组,并调用每个监听器函数
    listeners.forEach((fn) => fn());
  };

  // 返回一个包含getState、dispatch和subscribe的对象
  return { getState, dispatch, subscribe };
}

/**
 * 组合多个 reducer 函数
 *
 * @param reducers 一个包含多个 reducer 函数的对象
 * @returns 返回一个 reducer 函数,将多个 reducer 函数组合在一起
 */
export function combineReducers(reducers) {
  // 获取所有reducer的键名
  const keys = Object.keys(reducers);

  // 返回一个新的reducer函数
  return (state, action) => {
    // 创建一个新的状态对象
    const nextState = {};

    // 遍历所有reducer的键名
    keys.forEach((key) => {
      // 获取对应键名的reducer函数
      const reducer = reducers[key];

      // 获取当前状态中对应键名的值
      const prve = state[key];

      // 调用reducer函数,传入当前值和action,得到下一个状态的值
      const next = reducer(prve, action);

      // 将下一个状态的值添加到新的状态对象中
      nextState[key] = next;
    });

    // 返回新的状态对象
    return nextState;
  };
}

export const ReduxContext = createContext();

/**
 * 连接函数,用于将组件与 Redux store 连接起来
 *
 * @param mapStateToProps 将 Redux store 中的 state 映射到组件的 props
 * @param mapDispatchToProps 将 Redux store 中的 dispatch 方法映射到组件的 props
 * @returns 返回一个高阶组件,该高阶组件将传入的组件与 Redux store 连接起来
 */
export function connect(mapStateToProps, mapDispatchToProps) {
  // 返回一个函数,该函数接收一个组件作为参数
  return function (Component) {
    // 返回一个函数,该函数接收props作为参数
    return function (props) {
      // 使用useContext钩子获取Redux的store
      const store = useContext(ReduxContext);
      // 使用useState钩子创建一个状态变量,并初始化为false
      const [, setBool] = useState(false);

      // 定义一个forceUpdate函数,用于强制更新组件
      const forceUpdate = () => setBool((val) => !val);

      // 在组件挂载后,将forceUpdate函数作为监听器订阅store的更新
      useEffect(() => store.subscribe(forceUpdate), []);

      // 返回一个JSX元素,该元素渲染传入的组件,并传入props和绑定的state和dispatch
      return (
        <Component
          {...props}
          {...mapStateToProps(store.getState())}
          {...mapDispatchToProps(store.dispatch)}
        />
      );
    };
  };
}

src/store/index.js

import { createStore, combineReducers, connect } from "./redux";

function countReducers(count, action) {
  switch (action.type) {
    case "addCount":
      return count + 1;
    default:
      return count;
  }
}

function infoReducers(info, action) {
  switch (action.type) {
    case "addAge":
      return { ...info, age: info.age + 1 };
    default:
      return info;
  }
}

const initialValue = {
  count: 23,
  info: {
    name: "张三",
    age: 27,
  },
};

const reducers = combineReducers({ count: countReducers, info: infoReducers });

export const store = createStore(reducers, initialValue);

function mapStateToProps(state) {
  return { count: state.count, info: state.info };
}

function mapDispatchToProps(dispatch) {
  return {
    addCount() {
      dispatch({ type: "addCount" });
    },
    addAge() {
      dispatch({ type: "addAge" });
    },
  };
}

export const connected = connect(mapStateToProps, mapDispatchToProps);

某个 .jsx 文件内

import React from "react";
import { store, connected } from "../../store";
import { ReduxContext } from "../../store/redux";

const Child = connected(({ count, info, addCount, addAge }) => {
  return (
    <div>
      I am Child
      <div>
        <span>count:{count}</span>
        <button onClick={addCount}>addCount</button>
      </div>
      <div>
        <span>
          info:{info.name},{info.age}
          <button onClick={addAge}>addAge</button>
        </span>
      </div>
    </div>
  );
});

const Parent = () => (
  <div>
    I am Parent
    <Child />
  </div>
);

export default function Store() {
  return (
    <ReduxContext.Provider value={store}>
      <Parent />
    </ReduxContext.Provider>
  );
}

Mobx 基本原理(手撸简版)

可以发现跟Vue的响应式有点相似

// 具体实现:

let effect = null;
const deps = [];

function handle() {
  // 返回一个对象,包含两个方法:get 和 set
  return {
    // get 方法用于获取目标对象的属性值
    get(target, key, desc) {
      // 如果存在 effect,则将其添加到 deps 数组中
      if (effect) deps.push(effect);

      // 调用 Reflect.get 方法获取目标对象的属性值
      return Reflect.get(target, key, desc);
    },
    // set 方法用于设置目标对象的属性值
    set(target, value, key, desc) {
      // 调用 Reflect.set 方法设置目标对象的属性值
      Reflect.set(target, value, key, desc);

      // 遍历 deps 数组,依次执行其中的函数
      deps.forEach((dep) => dep());
    },
  };
}
/**
 * 遍历数据,递归处理对象和数组中的值
 *
 * @param data 要遍历的数据
 * @returns 返回处理后的数据
 */
function walk(data) {
  // 如果数据为空或者数据类型不是对象,则直接返回数据
  if (data === null || typeof data !== "object") return data;

  // 遍历对象的所有键值对
  Object.entries(data).forEach(([key, value]) => {
    // 递归调用 walk 函数,处理每个键值对的值,并将处理后的值重新赋值给对应的键
    data[key] = walk(value);
  });

  // 使用 Proxy 对象包装数据,并调用 handle 函数处理代理对象
  return new Proxy(data, handle());
}

/**
 * 将给定的数据转换为可观察对象
 *
 * @param data 要转换的数据
 * @returns 返回可观察对象
 */
function observable(data) {
  // 调用 walk 函数处理 data 参数,并返回处理结果
  return walk(data);
}

/**
 * 自动运行函数
 *
 * @param _effect 需要运行的函数
 */
function autorun(_effect) {
  // 将传入的参数_effect赋值给全局变量effect
  effect = _effect;

  // 调用全局变量effect对应的函数
  effect();

  // 将全局变量effect置为null
  effect = null;
}

// 具体使用:
const data = { count: 1 };

const store = observable(data);

autorun(() => {
  console.log("autorun store.count:", store.count);
});

store.count = 2; // 自动执行一次 autorun
store.count = 3; // 自动执行一次 autorun