如何用 useReducer + useContext 构建全局状态管理
在 React 开发中,当应用的状态逻辑变得复杂时,仅仅依靠 props
和回调函数已经难以胜任。这时,结合 useReducer
与 useContext
的组合,能够为我们提供一种强大的全局状态管理方案。
一、什么是 useReducer
?
useReducer
是 React 提供的一个 Hook,它用于管理复杂的状态逻辑。它适合处理,多个互相关联的状态值,包含多个子值的复杂对象状态,需要集中处理的状态更新逻辑。
基本语法:
jsx
const [state, dispatch] = useReducer(reducer, initialState);
state
:当前状态对象。dispatch
:一个函数,用于发送动作(action)来触发状态更新。reducer
:一个纯函数,根据 action 类型返回新的状态。initialState
:初始状态对象。
useReducer
的本质是一种状态管理模式 ,而useState是响应式状态。设计思想可以总结为三点,用 action
表达"我想做什么"而不是"我要怎么改";所有的状态更新逻辑都集中在reducer中;使用无副作用的纯函数进行状态更新,确保状态变化可预测、可测试。这种方式让状态逻辑更清晰、更易于调试、也更容易复用。
二、核心组成部分详解
-
initialState
:它是一个初始状态对象,是整个状态管理的起点,通常是一个包含多个字段的对象。这个对象代表了你的应用或组件的"数据模型"。
jsxconst initialState = { count: 0, isLogin: false, theme: 'light', };
-
reducer
函数在reducer中会根据动作类型处理对应业务逻辑并返回新的状态。
reducer
是一个纯函数,接收当前状态和一个动作对象,返回一个新的状态。纯函数的特点:相同输入一定返回相同输出、不产生副作用(如网络请求、修改外部变量)、不直接修改原状态(必须返回新状态)
示例:
jsxconst reducer = (state, action) => { switch (action.type) { case 'increment': return { ...state, count: state.count + 1 }; case 'decrement': return { ...state, count: state.count - 1 }; case 'toggleTheme': return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' }; default: return state; } };
在这个函数中,我们定义了每种动作类型对应的状态更新逻辑。state参数是当前的数据状态,action` 是一个对象,用来描述"发生了什么",从而告诉 reducer 如何更新状态。,结构如下:
jsaction = { type: '意图的类型', // payload: { name: 'Alice', id: 1 } } }
其中的type必须携带,表示的是状态更新的意图。playload是可以携带的额外数据,用于修改状态。
-
dispatch
函数dispatch
是由useReducer
返回的函数,用于向 reducer 发送动作。可以把它理解为:"我想要执行某个操作,请你(reducer)帮我更新状态"。dispatch接收的参数就是上述的action
jsx<button onClick={() => dispatch({ type: 'increment', playload: {} })}>+1</button>
三、 useReducer
与 useState
的对比
对比维度 | useState |
useReducer |
---|---|---|
状态类型 | 简单类型(number, string, boolean) | 复杂对象、多个子值 |
更新方式 | 直接设置新值 | 通过 action 触发 reducer |
可维护性 | 简单场景易用 | 复杂逻辑更清晰 |
可测试性 | 较难统一测试 | reducer 是纯函数,易于测试 |
可扩展性 | 多个 useState 分散 | 状态集中、逻辑统一 |
四、结合 useContext
实现全局状态管理
当多个组件需要访问和修改同一个状态时,我们可以将 useReducer
与 useContext
结合起来,实现类似 Redux 的状态管理模式。
这里以TodoList为例:
创建一个 TodoContext上下文
jsx
import { createContext } from "react";
// 创建上下文
export const TodoContext = createContext(null);
自定义一个名为useTodoContext的hook函数
这个函数的作用是用于返回TodoContext上下文。这样可以避免每次使用上下文时都需要使用useContext来获取
js
import { useContext } from "react";
import { TodoContext } from "../TodoContext";
// 自定义 hook 返回上下文
export function useTodoContext() {
return useContext(TodoContext);
}
自定义名为useTodos的hook函数
在这个hook中使用useReducer来管理状态 ,函数接收一个初始状态并交给useReducer来管理。hook函数通过使用useReducer返回的dispatch函数来定义不同的派遣任务函数,如addList、deleteList等。最后hook函数返回一个对象(对象中主要是数据状态,以及hook中定义的派遣函数)作上下文对象中的value,以便于在整个应用中使用。
js
import { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "add":
return [
...state,
{ id: Date.now(), container: action.payload.container, completed: false },
];
case "remove":
return state.filter((item) => item.id !== action.payload.id);
case "changeCompleted":
return state.map((item) => {
if (item.id === action.payload.id) {
return { ...item, completed: !item.completed };
}
return item;
});
default:
return state;
}
}
export function useTodos(inital = []) {
// 提供参数默认值
// value 是一个数组对象
const [todos, dispatch] = useReducer(reducer, inital);
const addList = (container) => {
dispatch({
type: "add",
payload: {
container,
},
});
};
const deleteList = (id) => {
dispatch({
type: "remove",
payload: {
id,
},
});
};
const changeCompleted = (id) => {
dispatch({
type: "changeCompleted",
payload: {
id,
},
});
};
return {
todos,
addList,
deleteList,
changeCompleted,
};
}
创建 Provider 组件,用于包裹整个应用,为应用提供上下文
jsx
import { TodoContext } from "./TodoContext"; // 引入上下文
import { useTodos } from "./hoosk/useTodos"; // 引入useTodo hook
export default function TodoProvider({ children }) {
const todosHook = useTodos([{id: 1,container: "吃饭",completed: false,},{id: 2,container: "睡觉",completed: true,},]);
return <TodoContext.Provider value={todosHook}>{children}</StoreContext.Provider>;
}
组件结构
根组件App.jsx
jsx
import "./App.css";
import TodoList from "./component/TodoList";
import TodoProvider from "./TodoProvider"
function App() {
return (
<>
<TodoProvider>
<TodoList />
<TodoProvider>
</>
);
}
export default App;
TodoList组件
jsx
import { useState } from "react";
import TodoForm from "./TodoForm";
import TodoItem from "./TodoItem";
function TodoList() {
const [title, setTitle] = useState("ToDoList");
return (
<div className="continer">
<h1 className="title">{title}</h1>
<TodoForm />
{/* 组件传参 */}
<TodoItem />
</div>
);
}
export default TodoList;
TodoForm组件
jsx
import { useState } from "react";
import { useTodoContext } from "../../../hoosk/useTodoContext";
function TodoForm() {
const [container, setContainer] = useState("");
const { addList } = useTodoContext();
// 提交
function handleSubmit(e) {
e.preventDefault();
if (container.trim()) {
addList(container.trim());
}
setContainer("");
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="请输入待办事项"
value={container}
onChange={(e) => {
setContainer(e.target.value);
}}
/>
<button type="submit">添加</button>
</form>
);
}
export default TodoForm;
TodoItem组件
jsx
import { useTodoContext } from "../../../hoosk/useTodoContext";
function TodoItem() {
const { todos, deleteList, changeCompleted } = useTodoContext();
return (
<ul>
{todos.map((item) => {
// 数据驱动界面 数据变化界面会变化
return (
<li key={item.id} style={{ textDecoration: item.completed ? "line-through" : "none" }}>
{item.container}
<input type="checkbox" checked={item.completed}
onChange={() => changeCompleted(item.id)}
/>
<button onClick={() => deleteList(item.id)}>删除</button>
</li>
);
})}
</ul>
);
}
export default TodoItem;
实现效果
