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

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() {
  // 初始化 useReducer
  const [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,
    };
    // 新增todo
    dispatch({
      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,
    };
    // 新增todo
    dispatch({
      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].

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gaog2zh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值