在React中,如何像处理本地状态一样处理全局状态(useState)?是否有一个方便的useGlobalState钩子可以用来读取全局存储对象,并在它更新时强制重新渲染所有订阅的组件,而不需要冗余的代码呢?没有Redux或Context参与,就这么简单。
一个用于组件的React钩子:
function Profile() {
const [user] = useGlobalState("user");
return (
<div className="Profile">
<h3>{user.name}</h3>
<p>{user.age}</p>
</div>
);
}
与useState相同的API — const [data, setData] = useGlobalState(storeSlice);
也可以在React组件外部访问全局状态:
// 在React组件外部访问全局存储
import { setGlobalState, getGlobalState } from "../store.config.js";
getGlobalState("user");
setGlobalState("user", {
name: "Johny Bo",
age: 23,
});
以上代码的存储配置非常简单。没有Redux-like的reducer、selector或action。只需要一个.js/ts配置文件(注意store.config.js的导入)来初始化存储。
// store.config.js
// 初始化全局存储
import createGlobalState from "./createGlobalState.js";
const initialState = {
user: {
name: "Justin Case",
age: 37,
},
};
export const { useGlobalState, setGlobalState, getGlobalState } =
createGlobalState(initialState);
你只需调用createGlobalState()并传入初始存储值,然后导出钩子和它返回的两个函数即可。
然后,在你的代码库中的任何地方/组件都可以从相同的配置导入这些函数来获取/设置存储数据。
import {
useGlobalState,
setGlobalState,
getGlobalState,
} from "../store.config.js";
createGlobalState()的JavaScript实现代码约为60行(不包括注释)。它具有从组件内外部获取和设置存储值的功能,并处理与特定存储片段“挂钩”的所有组件的更新(重新渲染)。
createGlobalState.js :
// createGlobalState.js
import { useState, useEffect } from "react";
function createGlobalState(initialState = {}) {
const state = initialState;
const subscribers = {};
// Subscribers per state slice/prop
// e.g. state.user
for (const i in state) {
subscribers[i] = [];
}
function useGlobalState(key) {
// To prevent getting/setting keys that aren't initialized
// May go away with the TypeScript implementation
if (!state.hasOwnProperty(key)) {
throw new Error("This key does not exist in the store");
}
// Global state as React local state
const [stateChunk, setStateChunk] = useState(state[key]);
useEffect(() => {
subscribers[key].push(setStateChunk);
// Cleanup: subscriber removal after effect execution
return () => {
const index = subscribers[key].findIndex((fn) => fn === setStateChunk);
subscribers[key].splice(index, 1);
};
}, [key]);
return [
stateChunk,
(value) => {
setGlobalState(key, value);
},
];
}
// Useful for setting state outside React components
const setGlobalState = (key, value = null) => {
// To prevent setting keys that aren't initialized
if (!state.hasOwnProperty(key)) {
throw new Error("This key does not exist in the store");
}
state[key] = value;
subscribers[key].forEach((subscriber) => {
subscriber(value);
});
};
// Useful for getting state outside React components
const getGlobalState = (key) => {
// To prevent getting keys that aren't initialized
if (!state.hasOwnProperty(key)) {
throw new Error("This key does not exist in the store");
}
return state[key];
};
return {
useGlobalState,
setGlobalState,
getGlobalState,
};
}
export default createGlobalState;
在处理钩子时,你可以确保存储配置文件和组件内部的类型安全:
// store.config.ts
import createGlobalState from "./createGlobalState";
export type User = {
name: string;
age: number;
};
type State = {
user: User | null;
};
export const initialState: State = {
user: {
name: "Justin Case",
age: 37,
},
};
export const { useGlobalState, setGlobalState, getGlobalState } =
createGlobalState<State>(initialState);
组件中的类型安全:
// Profile.tsx
import { useGlobalState, type User } from "../store.config.js";
function Profile() {
const [user, setUser] = useGlobalState<User>("user");
const [name, setName] = useState(user?.name);
const [age, setAge] = useState(user?.age);
const handleSubmit = () => {
setUser({ name, age });
};
return (
<form
onSubmit={() => {
setUser({ name, age });
}}
>
<input
type="text"
name="name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
name="age"
value={age}
onChange={(e) => setAge(e.target.value)}
/>
</form>
);
}
下面的示例(codesandbox)展示了如何使用useGlobalState来获取和更新存储的一部分,并强制所有订阅的组件在更改时重新渲染。
在实验中没有包括,但使用setGlobalState(非组件方式)修改状态也会重新渲染任何订阅的组件。
测试代码:
// Profile.js
import { useGlobalState } from "../store.config";
function Profile() {
const [user] = useGlobalState("user");
return (
<div className="Profile">
<h3>{user.name}</h3>
<p>{user.age}</p>
</div>
);
}
// Profile.test.js
import { render, screen } from "@testing-library/react";
import Profile from "./Profile";
import { setGlobalState } from "../store.config";
it("should display global user data", () => {
setGlobalState("user", {
name: "Chris Stone",
age: 20,
});
render(<Banner />);
expect(screen.queryByText("Chris Stone")).toBeInTheDocument();
expect(screen.queryByText("20")).toBeInTheDocument();
});
还有一些库的测试(createGlobalState.test.tsx)可在这里找到。
这种基本全局存储实现的思想并不新鲜。它受到类似项目(如react-hook-global-state)的启发,但没有引入外部依赖。
尽管简单,createGlobalState也是可扩展的。开发人员在流行的全局状态解决方案中所欣赏的功能,例如中间件、错误处理、actions等,都可以在其之上构建。
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。