react 轻量的全局状态管理

657 阅读2分钟

近期,我们公司启动了一个小型项目,本人旨在拓展公司技术栈(尝试和体验Vite这一构建工具)。因此,决定摒弃一直沿用的Umi全家桶,转而采用Vite + React的组合。

在Umi的框架中,有一个非常实用的插件@umijs/plugin-model,它采用hook的方式提供了全局状态管理的功能。然而,由于我们此次选择了Vite而非Umi,这个插件便无法直接应用。同时,我们也对使用connect模式来管理组件状态持保留态度,因为它在结合forwardRefTypeScript时显得不够灵活和直观。

为了解决这个问题,所以就打算自己写一个轻量级的全局状态管理工具。主要设计思路如下:

  1. 利用闭包来存储全局变量以及一个用于更新状态的set函数数组,并对外提供一个hook。
  2. 这个自定义hook类似于React的useState,返回包含state(即闭包中的全局变量)和setState(用于更新状态并通知所有使用该hook的组件重新渲染)的方法。
  3. 当任何组件使用这个hook时,它都会在set函数数组中保存一份更新状态的方法,确保状态变更时能够触发所有相关组件的重新渲染。

通过这种方法,我们能够在不使用Umi全家桶的情况下,实现一个既轻量又高效的全局状态管理方案。

同时,为了减少首屏的接口请求数量,只有在返回的hook被使用到时才会调用接口去获取初始值,做了一些额外的操作。

代码如下

import { useEffect, useState } from "react";

const store = <T>(
  initState: T | (() => Promise<T>) | (() => T)
): (() => [T, React.Dispatch<React.SetStateAction<T>>]) => {
  let _state: T;
  const _updaters: React.Dispatch<React.SetStateAction<T>>[] = [];

  let asyncInitState: (() => Promise<void>) | null = null;
  if (typeof initState !== "function") {
    _state = initState;
  } else if (
    Object.prototype.toString.call(initState) === "[object Function]"
  ) {
    _state = (initState as () => T)();
  } else if (
    Object.prototype.toString.call(initState) === "[object AsyncFunction]"
  ) {
    asyncInitState = () =>
      (initState as () => Promise<T>)().then((state) => {
        _state = state;
        _updaters.forEach((updater) => updater(_state));
      });
  }

  return () => {
    const [value, setValue] = useState(_state);

    useEffect(() => {
      _updaters.push(setValue);
      if (asyncInitState) {
        asyncInitState();
        asyncInitState = null;
      }
      return () => {
        const index = _updaters.indexOf(setValue);
        if (index >= 0) {
          _updaters.splice(index, 1);
        }
      };
    }, []);

    const update: typeof setValue = (state) => {
      if (typeof state === "function") {
        _state = (state as (prevState: T) => T)(_state);
      } else {
        _state = state;
      }
      _updaters.forEach((updater) => updater(_state));
    };

    return [value, update];
  };
};

export default store;