近期,我们公司启动了一个小型项目,本人旨在拓展公司技术栈(尝试和体验Vite这一构建工具)。因此,决定摒弃一直沿用的Umi全家桶,转而采用Vite + React的组合。
在Umi的框架中,有一个非常实用的插件@umijs/plugin-model,它采用hook的方式提供了全局状态管理的功能。然而,由于我们此次选择了Vite而非Umi,这个插件便无法直接应用。同时,我们也对使用connect模式来管理组件状态持保留态度,因为它在结合forwardRef和TypeScript时显得不够灵活和直观。
为了解决这个问题,所以就打算自己写一个轻量级的全局状态管理工具。主要设计思路如下:
- 利用闭包来存储全局变量以及一个用于更新状态的set函数数组,并对外提供一个hook。
- 这个自定义hook类似于React的
useState,返回包含state(即闭包中的全局变量)和setState(用于更新状态并通知所有使用该hook的组件重新渲染)的方法。 - 当任何组件使用这个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;