当前位置: 首页 > news >正文

0901context_useReducer_状态管理-react-仿低代码平台项目

文章目录

    • 1 React状态管理概述
      • 1.1 why&what
      • 1.2 React 内置状态管理
      • 1.3 主流第三方库
        • 1.3.1 Redux
        • 1.3.2 MobX
        • 1.3.3 Recoil
        • 1.3.4 Zustand
      • 1.4 选型建议
      • 1.5 高级模式
      • 1.6 常见问题
      • 1.7 总结
    • 2 Context
      • 2.1 Context 的核心作用
        • 2.1.1 解决的问题
        • 2.1.2 核心三要素
      • 二、基础使用示例
        • 1. 创建 Context
        • 2. 提供 Context
        • 3. 消费 Context
      • 三、性能优化策略
        • 1. **避免不必要的渲染**
        • 2. **精细化更新**
        • 3. **结合 `React.memo`**
      • 四、适用场景 vs 不适用场景
      • 五、常见问题
        • 1. **未提供 Provider 导致默认值失效**
        • 2. **动态 Context 导致 TypeScript 类型错误**
        • 3. **异步更新问题**
      • 六、Context 与其他方案的对比
      • 总结
    • 3 useReducer
      • 3.1 基础概念
        • 3.1.1 与 `useState` 的对比
        • 3.1.2 核心三要素
      • 3.2 基础使用示例
        • 3.2.1 计数器场景
        • 3.2.2 复杂状态管理(待办事项)
      • 3. 高级用法
        • 3.1.1 延迟初始化(Lazy Initialization)
        • 3.1.2 与 Context API 结合
      • 3.4 适用场景 vs 不适用场景
      • 3.5 最佳实践
      • 3.6 常见问题
        • 3.6.1 如何处理异步操作?
        • 3.6.2 如何避免重复渲染?
        • 3.6.3 为什么 reducer 必须是纯函数?
      • 3.7 与 Redux 的对比
      • 3.8 总结
    • 4 todoList
    • 结语

1 React状态管理概述

React 的状态管理体系是构建复杂应用的核心,随着应用规模的扩大,合理管理状态至关重要。以下是 React 状态管理的核心方案及其适用场景的详细总结:


1.1 why&what

状态提升

  • 页面拆分组件
  • 数组存储在父组件
  • 通过props传递给子组件

状态管理

  • 页面足够复杂:组件多,嵌套深
  • 通过props层层传递不合适
  • 需要状态管理,即集中、统一管理页面数据

1.2 React 内置状态管理

  1. 组件内状态(useState/useReducer)

    • 适用场景:组件内部独立状态(如表单输入、UI 交互状态)。
    • 优点:轻量、直接,无需依赖外部库。
    • 缺点:状态无法跨组件共享,深层传递需结合其他方案。
  2. Context API

    • 作用:跨层级组件共享状态(如主题、用户信息)。

    • 实现

      const ThemeContext = createContext();
      // 提供状态
      <ThemeContext.Provider value={theme}><ChildComponent />
      </Provider>
      // 消费状态
      const theme = useContext(ThemeContext);
      
    • 注意点

      • 性能问题:Provider 值变化会导致所有消费者重新渲染,需配合 useMemo 或状态分离优化。
      • 不适合高频更新:频繁变更的状态(如表单全局状态)可能导致性能问题。

1.3 主流第三方库

1.3.1 Redux
  • 核心概念

    • 单一 Store:全局状态集中管理。
    • Action → Reducer → Store:通过纯函数更新状态。
    • Middleware:处理异步逻辑(如 redux-thunkredux-saga)。
  • 适用场景:大型应用、需要状态追溯或时间旅行调试。

  • Redux Toolkit 简化方案

    const counterSlice = createSlice({name: 'counter',initialState: 0,reducers: {increment: state => state + 1,},
    });
    const store = configureStore({ reducer: counterSlice.reducer });
    
1.3.2 MobX
  • 设计理念:响应式编程,通过 observable 自动追踪状态变化。
  • 核心 API
    • observable:标记可观察状态。
    • action:定义状态修改方法。
    • autorun:自动响应状态变化。
  • 优点:代码简洁,适合快速开发。
  • 缺点:过度灵活可能导致状态流难以追踪。
1.3.3 Recoil
  • 核心概念

    • Atom:状态原子单位,可跨组件订阅。
    • Selector:派生状态,支持异步计算。
  • 特点:Hooks 风格 API,与 React 深度集成。

    const fontSizeState = atom({ key: 'fontSize', default: 14 });
    const fontSizeLabel = selector({key: 'fontSizeLabel',get: ({get}) => `${get(fontSizeState)}px`,
    });
    
1.3.4 Zustand
  • 特点:轻量级,基于 Hook 的状态管理。

  • 示例

    const useStore = create((set) => ({count: 0,increment: () => set(state => ({ count: state.count + 1 })),
    }));
    // 使用
    const { count, increment } = useStore();
    

1.4 选型建议

方案适用场景复杂度学习曲线性能
Context低频更新的全局状态(主题/用户)需优化
Redux大型项目,严格状态管理优秀
MobX快速开发,响应式需求优秀
Recoil复杂派生状态,React 18+ 生态优秀
Zustand中小项目,极简 API优秀

1.5 高级模式

  1. 状态持久化:结合 localStorage 或库(如 redux-persist)。
  2. 服务端状态:使用 React QuerySWR 管理异步请求和缓存。
  3. 状态分割:按业务模块拆分 Store 或 Context,避免单一 Store 膨胀。

1.6 常见问题

  1. 状态提升 vs 状态下沉:根据组件层级决定状态存放位置。
  2. 避免冗余渲染:使用 React.memouseMemo 或状态库的精细化更新(如 Redux 的 shallowEqual)。
  3. TypeScript 支持:优先选择强类型方案(如 Recoil、Zustand 对 TS 友好)。

1.7 总结

React 状态管理没有“银弹”,选择需权衡项目规模、团队习惯和性能需求。小型项目优先使用 Context 或 Zustand,大型复杂应用推荐 Redux Toolkit 或 Recoil,追求开发速度可考虑 MobX。始终遵循“最小状态原则”,避免过度设计。

2 Context

React 的 Context API 是官方提供的跨层级组件状态共享方案,适用于解决组件树中多层级组件间的数据传递问题。以下是 Context 的 核心概念、使用场景、最佳实践和常见问题 的深度解析:


2.1 Context 的核心作用

2.1.1 解决的问题
  • Props Drilling(属性透传):避免通过多层级组件手动传递 props。
  • 全局共享状态:如主题(Theme)、用户身份(Auth)、多语言(i18n)等。
2.1.2 核心三要素
  • createContext(defaultValue):创建上下文对象(包含 Provider 和 Consumer)。
  • <Context.Provider value={...}>:包裹组件树,提供共享数据。
  • useContext(Context):在子组件中消费上下文数据(或通过 Consumer 类组件)。

二、基础使用示例

1. 创建 Context

ThemeContext.ts:

import { createContext } from 'react';export const ThemeContext = createContext({theme: 'light',toggleTheme: () => {}, // 默认空函数(避免未定义调用)
});
2. 提供 Context

index.tsx:

import { FC, useState } from "react";
import { ThemeContext } from "./ThemeContext";
import ChildComponent from "./ChildComponent";const Demo: FC = () => {const [theme, setTheme] = useState("light");const toggleTheme = () => {setTheme((prev) => (prev === "light" ? "dark" : "light"));};return (<ThemeContext.Provider value={{ theme, toggleTheme }}><ChildComponent /></ThemeContext.Provider>);
};export default Demo;
3. 消费 Context

ChildComponent.tsx:

import { FC, useContext } from "react";
import { ThemeContext } from "./ThemeContext";const Demo: FC = () => {const { theme, toggleTheme } = useContext(ThemeContext);return (<div style={{ background: theme === "light" ? "#fff" : "#333" }}><button onClick={toggleTheme}>Toggle Theme</button></div>);
};export default Demo;

三、性能优化策略

1. 避免不必要的渲染
  • 问题:Provider 的 value 值变化时,所有消费该 Context 的组件都会重新渲染,即使它们只依赖部分数据。

  • 解决方案

    • 拆分多个 Context:将高频更新和低频更新的状态分离。

      // 拆分主题和用户信息
      <ThemeProvider><UserProvider><App /></UserProvider>
      </ThemeProvider>
      
    • 使用 useMemo 缓存对象:防止 Provider 的 value 对象每次渲染重新创建。

      const themeValue = useMemo(() => ({ theme, toggleTheme }), [theme]);
      return <ThemeContext.Provider value={themeValue}>...</Provider>;
      
2. 精细化更新
  • 问题:组件只关心 Context 中的部分数据(如 theme 不关心 toggleTheme 函数)。

  • 解决方案:将状态和更新方法分离到不同 Context。

    // ThemeStateContext 和 ThemeDispatchContext
    const [theme, setTheme] = useState('light');
    <ThemeStateContext.Provider value={theme}><ThemeDispatchContext.Provider value={setTheme}><ChildComponent /></ThemeDispatchContext.Provider>
    </ThemeStateContext.Provider>
    
3. 结合 React.memo
const ExpensiveComponent = React.memo(() => {const theme = useContext(ThemeContext);// 仅当 theme 变化时重新渲染
});

四、适用场景 vs 不适用场景

适用场景不适用场景
低频更新的全局配置(主题/语言)高频更新状态(如实时输入表单)
跨多层级的只读数据传递复杂业务逻辑(需结合 useReducer)
简单共享工具类(路由/弹窗)需要状态历史追踪或中间件

五、常见问题

1. 未提供 Provider 导致默认值失效
  • 原因createContext(defaultValue) 的默认值仅在 未找到匹配的 Provider 时生效。
  • 解决:始终在顶层包裹 Provider。
2. 动态 Context 导致 TypeScript 类型错误
  • 解决方案:明确类型定义。

    interface ThemeContextType {theme: 'light' | 'dark';toggleTheme: () => void;
    }
    const ThemeContext = createContext<ThemeContextType>({} as ThemeContextType); // 强制断言(慎用)
    
3. 异步更新问题
  • 场景:在异步回调中更新 Context 值可能导致闭包陷阱。

  • 解决:使用 useRef 保持最新引用。

    const themeRef = useRef(theme);
    themeRef.current = theme; // 每次渲染更新 ref
    const toggleTheme = () => {setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
    };
    

六、Context 与其他方案的对比

方案优点缺点
Context官方原生,无需额外依赖性能需手动优化,不适合高频更新
Redux严格状态管理,支持中间件和时间旅行代码模板冗余,学习成本高
Zustand轻量,自动优化渲染功能相对简单,生态较小

总结

  • 何时使用 Context:跨层级共享 低频更新 的全局状态(如主题、用户信息)。
  • 何时不用 Context:高频更新、复杂状态逻辑或需要中间件支持时,考虑结合 useReducer 或使用 Redux/Zustand。
  • 最佳实践:拆分多个 Context + useMemo 缓存 + 类型安全(TypeScript)。

通过合理优化,Context 可以成为 React 应用中高效的状态共享工具,但需警惕过度使用导致的性能问题。

3 useReducer

React 的 useReducer 是用于管理复杂组件状态的内置 Hook,尤其适用于状态逻辑包含多个子值或依赖前一个状态的场景。以下是 useReducer核心用法、适用场景、优化技巧 的深度解析:


3.1 基础概念

3.1.1 与 useState 的对比
特性useStateuseReducer
适用场景简单独立状态(如布尔值、数字)复杂状态逻辑(如对象、数组、多操作)
状态更新逻辑直接修改通过 dispatch 触发预定义规则更新
可维护性简单但易混乱逻辑集中,更易调试和测试
3.1.2 核心三要素
  • reducer 函数:接收当前状态和动作(action),返回新状态(必须纯函数)。
  • initialState:状态的初始值。
  • dispatch 方法:触发状态更新的函数(发送 action)。

3.2 基础使用示例

3.2.1 计数器场景
import { useReducer } from 'react';// 定义 reducer 函数
function counterReducer(state, action) {switch (action.type) {case 'increment':return { count: state.count + 1 };case 'decrement':return { count: state.count - 1 };case 'reset':return { count: 0 };default:throw new Error('Unknown action type');}
}function Counter() {// 初始化 useReducerconst [state, dispatch] = useReducer(counterReducer, { count: 0 });return (<div><p>Count: {state.count}</p><button onClick={() => dispatch({ type: 'increment' })}>+1</button><button onClick={() => dispatch({ type: 'decrement' })}>-1</button><button onClick={() => dispatch({ type: 'reset' })}>Reset</button></div>);
}
3.2.2 复杂状态管理(待办事项)
const todoReducer = (state, action) => {switch (action.type) {case 'ADD_TODO':return [...state, { id: Date.now(), text: action.text, done: false }];case 'TOGGLE_TODO':return state.map(todo =>todo.id === action.id ? { ...todo, done: !todo.done } : todo);case 'DELETE_TODO':return state.filter(todo => todo.id !== action.id);default:return state;}
};function TodoList() {const [todos, dispatch] = useReducer(todoReducer, []);// 使用 dispatch({ type: 'ADD_TODO', text: 'Learn React' }) 等操作
}

3. 高级用法

3.1.1 延迟初始化(Lazy Initialization)
  • 适用场景:初始状态需要复杂计算时,避免每次渲染重复计算。
  • 实现方式:传递初始化函数作为第三个参数。
const init = (initialCount) => ({ count: initialCount });function reducer(state, action) {switch (action.type) {case 'increment':return { count: state.count + 1 };case 'decrement':return { count: state.count - 1 };case 'reset':return init(action.payload);default:throw new Error();}
}function Counter({ initialCount }) {const [state, dispatch] = useReducer(reducer, initialCount, init);// ...
}
3.1.2 与 Context API 结合
  • 场景:跨组件共享复杂状态逻辑。
// 创建 Context
const TodoContext = createContext();function TodoProvider({ children }) {const [todos, dispatch] = useReducer(todoReducer, []);return (<TodoContext.Provider value={{ todos, dispatch }}>{children}</TodoContext.Provider>);
}// 子组件中消费
function AddTodo() {const { dispatch } = useContext(TodoContext);const [text, setText] = useState('');const handleSubmit = () => {dispatch({ type: 'ADD_TODO', text });setText('');};return (<input value={text} onChange={(e) => setText(e.target.value)} /><button onClick={handleSubmit}>Add</button>);
}

3.4 适用场景 vs 不适用场景

适用场景不适用场景
状态逻辑复杂(多操作、依赖前状态)简单状态(如布尔值切换)
需要状态历史追溯或回退独立组件内部简单交互
与 Context 结合实现全局状态管理无共享需求的局部状态

3.5 最佳实践

  1. 保持 Reducer 纯净

    • 禁止在 reducer 中执行副作用(如 API 调用)。
    • 直接修改 state(需返回新对象/数组)。
  2. 类型安全(TypeScript)

    type Action =| { type: 'increment' }| { type: 'decrement' }| { type: 'reset'; payload: number };function reducer(state: State, action: Action): State {// ...
    }
    
  3. 拆分复杂 Reducer

    // 将大型 reducer 拆分为多个子 reducer
    function rootReducer(state, action) {return {todos: todoReducer(state.todos, action),counter: counterReducer(state.counter, action),};
    }
    

3.6 常见问题

3.6.1 如何处理异步操作?
  • 方案一:在 dispatch 前处理异步逻辑。

    const fetchData = async () => {const data = await api.get();dispatch({ type: 'LOAD_DATA', payload: data });
    };
    
  • 方案二:结合中间件(需使用 Redux)。

3.6.2 如何避免重复渲染?
  • 使用 useMemo 缓存值

    const memoizedState = useMemo(() => state, [state.specificProp]);
    
3.6.3 为什么 reducer 必须是纯函数?
  • React 依赖纯函数特性实现状态更新预测性,副作用会导致渲染不一致。

3.7 与 Redux 的对比

特性useReducerRedux
作用范围组件内或 Context 共享全局 Store
中间件不支持支持(如 thunk、saga)
调试工具无内置工具Redux DevTools 时间旅行
学习曲线低(仅需理解 reducer 模式)高(需掌握 Action、Store 等)

3.8 总结

  • 何时使用 useReducer:组件状态逻辑复杂、需要集中管理操作、与 Context 结合共享状态。
  • 替代方案选择:简单状态用 useState,全局复杂状态用 Redux/Zustand。
  • 核心原则:保持 reducer 纯净,合理拆分逻辑,优先考虑 TypeScript 类型安全。

4 todoList

store.ts代码如下:

import { nanoid } from "nanoid";export type TodoType = {id: string;title: string;
};//初始化的todo list
const initialState: TodoType[] = [{id: nanoid(5),title: "吃饭",},{id: nanoid(5),title: "睡觉",},
];export default initialState;

reducer.ts代码如下:

import { TodoType } from "./store";export type ActionType = {type: string;payload?: any;
};export default function reducer(state: TodoType[], action: ActionType) {switch (action.type) {case "add":return state.concat(action.payload);case "delete":return state.filter((todo) => todo.id !== action.payload);default:throw new Error("不支持的操作!");}
}

列表页List.tsx代码如下:

import { FC, useReducer } from "react";
import initialState from "./store";
import reducer from "./reducer";const List: FC = () => {const [state, dispatch] = useReducer(reducer, initialState);function del(id: string) {dispatch({ type: "delete", payload: id });}return (<ul>{state.map((todo) => (<li key={todo.id}><span>{todo.title}</span><button onClick={() => del(todo.id)}>删除</button></li>))}</ul>);
};export default List;

输入表单也InputForm.tsx代码如下所示:

import { ChangeEvent, FC, useReducer, useState } from "react";
import initialState from "./store";
import reducer from "./reducer";
import { nanoid } from "nanoid";const InputForm: FC = () => {const [state, dispatch] = useReducer(reducer, initialState);// 输入框const [text, setText] = useState("");function handleChange(event: ChangeEvent<HTMLInputElement>) {setText(event.target.value);}function handleSubmit(event: ChangeEvent<HTMLFormElement>) {event.preventDefault();if (!text.trim()) {return;}const newTodo = {id: nanoid(5),title: text,};// 新增tododispatch({type: "add",payload: newTodo,});setText("");}return (<form onSubmit={handleSubmit}><label htmlFor="new-todo">新增代办事项</label><br /><input id="new-todo" onChange={handleChange} value={text} /><button type="submit">添加 #{state.length + 1}</button></form>);
};export default InputForm;

TodoReducer/index.tsx代码如下所示:

import { FC } from "react";
import List from "./List";
import InputForm from "./InputForm";const Demo: FC = () => {return (<><p>Todo list by useReducer</p><List /><InputForm /></>);
};export default Demo;

删除功能正常,但是点击添加,未实现预期效果,因为List里面的state和InputForm里面的tstate不是同一个state,这里需要使用Context进行状态共享,改造如下:

index.tsx代码如下:

import { createContext, FC, useReducer } from "react";
import List from "./List";
import InputForm from "./InputForm";
import initialState from "./store";
import reducer, { ActionType } from "./reducer";export const TodoContext = createContext({state: initialState,dispatch: (action: ActionType) => {},
});const Demo: FC = () => {const [state, dispatch] = useReducer(reducer, initialState);return (<TodoContext.Provider value={{ state, dispatch }}><p>Todo list by useReducer</p><List /><InputForm /></TodoContext.Provider>);
};export default Demo;

List.tsx代码如下所示:

import { FC, useContext } from "react";
import { TodoContext } from "./index";const List: FC = () => {const { state, dispatch } = useContext(TodoContext);// const [state, dispatch] = useReducer(reducer, initialState);function del(id: string) {dispatch({ type: "delete", payload: id });}return (<ul>{state.map((todo) => (<li key={todo.id}><span>{todo.title}</span><button onClick={() => del(todo.id)}>删除</button></li>))}</ul>);
};export default List;

InputForm.tsx代码如下所示:

import { ChangeEvent, FC, useContext, useState } from "react";
import { nanoid } from "nanoid";
import { TodoContext } from "./index";const InputForm: FC = () => {const { state, dispatch } = useContext(TodoContext);// const [state, dispatch] = useReducer(reducer, initialState);// 输入框const [text, setText] = useState("");function handleChange(event: ChangeEvent<HTMLInputElement>) {setText(event.target.value);}function handleSubmit(event: ChangeEvent<HTMLFormElement>) {event.preventDefault();if (!text.trim()) {return;}const newTodo = {id: nanoid(5),title: text,};// 新增tododispatch({type: "add",payload: newTodo,});setText("");}return (<form onSubmit={handleSubmit}><label htmlFor="new-todo">新增代办事项</label><br /><input id="new-todo" onChange={handleChange} value={text} /><button type="submit">添加 #{state.length + 1}</button></form>);
};export default InputForm;

效果如下图所示:在这里插入图片描述

结语

❓QQ:806797785

⭐️仓库地址:https://gitee.com/gaogzhen

⭐️仓库地址:https://github.com/gaogzhen

[1]react官网[CP/OL].

相关文章:

  • Django 学习指南:从入门到精通(大体流程)
  • 健康养生:构建健康生活的多维度指南
  • 扩展根分区
  • Word中批量修改MathType公式
  • 完美解决react-native文件直传阿里云oss问题一
  • 港口危货储存单位主要安全管理人员考试精选题目
  • K8S - HPA + 探针实战 - 实现弹性扩缩与自愈
  • springboot框架常用配置
  • Microsoft Entra ID 详解:现代身份与访问管理的核心
  • 《PyTorch documentation》(PyTorch 文档)
  • 学习记录:DAY21
  • 深度解析:Vue.js 性能优化全景指南(从原理到实践)
  • 破局 AI 焦虑:企业如何抢占智能时代的制高点
  • DC-DC常见应用问题解疑
  • 2025年CC攻击防御全攻略:应对复杂化攻击的实战策略
  • DeepSeek基础-使用python请求deepseek
  • 2025华东杯A/B/C题解题思路+可运行代码参考
  • 从 “可办“ 到 “好办“:云蝠大模型如何重塑政务服务体验
  • ubuntu下一些环境配置
  • 插入到word里面的用origin画的图,怎么获取图片细节?
  • 西部航空回应飞机上卖彩票:与重庆福彩合作,仅部分航班售卖
  • 据报特斯拉寻找新CEO,马斯克财报会议上表态:把更多时间投入特斯拉
  • 宿州市委副书记任东已任市政府党组书记
  • 居委业委居民群策群力,7位一级演员来到上海一小区唱戏
  • 美国务院宣布新一轮与伊朗相关的制裁
  • 2025五一档新片电影总票房破亿