如何在React中使用自定义Hook管理全局状态
在构建React应用时,我们经常需要跨多个组件共享状态。虽然Redux等状态管理库在大型应用中表现出色,但对于小到中等规模的项目来说,它们可能显得过于复杂。在这篇文章中,我将向你展示如何使用React的useState和useContext钩子创建自定义Hook来管理全局状态,同时演示如何有效地执行批量状态更新以防止状态覆盖
创建Context和Provider
首先,我们需要创建一个上下文和一个对应的提供者(Provider)来传递状态。
store 文件夹中定义index.tsx文件
import { createContext, ReactNode, useContext, useState } from "react";
import { DefinedState, initState } from "./defined.ts";
/* 定义 SetStateAction */
type SetStateAction = {
[P in keyof DefinedState]?: DefinedState[P];
};
// 定义 context 的类型
interface ContextType {
state: DefinedState;
setState: (action: SetStateAction) => void;
}
// 使用断言定义一个“空”的 context,并赋予后面会使用的类型
const ContextBody = createContext<ContextType>({} as ContextType);
export function ContentProvider({ children }: { children: ReactNode }) {
const [state, changeState] = useState<any>(initState);
const setState = (
action: SetStateAction | ((prevState: DefinedState) => SetStateAction)
) => {
changeState((prevState: DefinedState) => {
// 如果action是一个函数,传递prevState给这个函数来获取新状态
const newState =
typeof action === "function" ? action(prevState) : action;
// 合并旧状态与新状态
return { ...prevState, ...newState };
});
};
return (
<ContextBody.Provider value={{ state, setState }}>
{children}
</ContextBody.Provider>
);
}
export function useStateHook() {
return useContext(ContextBody);
}
store文件下创建defined.ts文件
/* 定义state数据类型和默认值 */
/** state数据集合 */
export interface DefinedState {
/* 测试数据 */
test1: string;
/* 测试数据2 */
test2: string;
}
/* 将所有类型变成可选 */
type OptionalDefinedState = Partial<DefinedState>;
export const initState: OptionalDefinedState = {
test1: '我是测试数据',
test2: '我是测试数据2',
};
在组件中使用自定义Hook
在我们的APP.tsx中,将我们的hook包裹这个组件
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { ContentProvider } from "./store/index.tsx";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<ContentProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</ContentProvider>
);
在我们开发过程中我们只需要引用我们的仓库即可操作获取数据,使用这个自定义Hook,组件可以访问共享状态并可以更新它。
import React from "react";
import { useStateHook } from "./store/index";
import { Input } from "antd";
function App() {
const { state, setState } = useStateHook();
const { test1, test2 } = state;
return (
<>
{test1}
<Input
placeholder="输入test1"
onPressEnter={(e: any) => {
setState({ test1: e.target.value });
setState({ test2: e.target.value });
}}
/>
<br />
{test2}
<Input
placeholder="请输入test2"
onPressEnter={(e: any) => setState({ test2: e.target.value })}
/>
<br />
</>
);
}
export default App;
Redux和使用useState与useContext结合的自定义Hook方法是用于在React应用中管理状态的两种不同方案。它们各有优缺点,并分别适合于不同的场景
比较
Redux
优点:
- 预测性强:Redux通过限制更新逻辑并通过纯函数来执行,提供了一个可以预测的状态更新方法。
- 中央化管理:Redux提供一个单一的状态树来管理应用的所有状态,使得状态管理更加一致和透明。
- 强大的中间件生态:Redux有着丰富的中间件,可以轻易地处理例如异步操作、日志记录等高级场景。
- 开发工具:Redux有很好的开发调试工具,例如Redux DevTools。
社区和生态系统:Redux有一个大且成熟的社区支持,以及大量的教程、例子和最佳实践。
缺点:
- 引入了额外的复杂性和引导概念,有一定的学习曲线。
对于小型或简单的应用来说,可能过于笨重。 - 代码样板较多,需要编写额外的action creators、reducers等。
需要遵守特定的Redux框架代码结构,可能需要调整代码设计。
适用场景:
- 大型或复杂的应用,尤其是需要多个组件访问同一状态的场景。
- 需要强大的状态管理功能,例如异步逻辑、日志记录、持久化、服务器端渲染等。
- 应用中有复杂的数据处理和业务逻辑的地方。
使用useState与useContext结合的自定义Hook方法
优点:
- 简化状态管理:较少的样板代码,没有那么多固定的模式,更简洁的API。
- 更符合React的函数式编程模式:直接使用React的内置Hook,不需要引入额外的库。
- 灵活性高:容易修改和维护状态逻辑。
- 状态逻辑集中在组件中,易于理解。
缺点:
- 对于复杂的全局状态管理,可能导致Provider组件层级过深。
- 缺少中间件生态系统,不那么容易处理复杂的异步流和副作用。
- 状态更新逻辑可能散布于组件间,导致难以追踪和理解。
- 没有Redux DevTools这样强大的开发工具来进行时光机式的调试。
适用场景:
- 中小型的应用,或状态相对简单的应用。
- 当Reducer逻辑较简单,且不需要太多中间件来处理复杂业务逻辑时。
- 项目开始阶段或原型开发,需要快速搭建状态管理而不引入复杂性。
总的来说,Redux是一个功能强大的全局状态管理解决方案,适合那些需要可预测且复杂的状态更新逻辑的大型应用。而自定义Hook结合useState和useContext的方案是一个更轻量级、简单的状态管理方法,非常适合小到中型应用或者那些需要快速开发和原型制作的项目。选择哪个方案应基于项目的具体需求和开发团队的熟悉度。