React 中hooks之useReducer使用场景和方法总结

1. useReducer 基本概念

useReducer 是 React 的一个 Hook,用于管理复杂的状态逻辑。它接收一个 reducer 函数和初始状态,返回当前状态和 dispatch 函数。

1.1 基本语法

const [state, dispatch] = useReducer(reducer, initialState, init);
  • reducer: (state, action) => newState
  • initialState: 初始状态
  • init: (可选) 惰性初始化函数

2. 基础示例

2.1 简单计数器

function counterReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}

3. 复杂状态管理示例

3.1 待办事项列表

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, {
          id: Date.now(),
          text: action.payload,
          completed: false
        }]
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
    default:
      return state;
  }
};

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, { todos: [] });
  const [input, setInput] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!input.trim()) return;
    dispatch({ type: 'ADD_TODO', payload: input });
    setInput('');
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={e => setInput(e.target.value)}
          placeholder="添加待办事项"
        />
        <button type="submit">添加</button>
      </form>
      <ul>
        {state.todos.map(todo => (
          <li key={todo.id}>
            <span
              style={{
                textDecoration: todo.completed ? 'line-through' : 'none'
              }}
              onClick={() => dispatch({
                type: 'TOGGLE_TODO',
                payload: todo.id
              })}
            >
              {todo.text}
            </span>
            <button onClick={() => dispatch({
              type: 'DELETE_TODO',
              payload: todo.id
            })}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

3.2 表单状态管理

const formReducer = (state, action) => {
  switch (action.type) {
    case 'SET_FIELD':
      return {
        ...state,
        [action.field]: action.value
      };
    case 'SET_ERROR':
      return {
        ...state,
        errors: {
          ...state.errors,
          [action.field]: action.error
        }
      };
    case 'RESET_FORM':
      return initialState;
    default:
      return state;
  }
};

function ComplexForm() {
  const initialState = {
    username: '',
    email: '',
    password: '',
    errors: {}
  };

  const [state, dispatch] = useReducer(formReducer, initialState);

  const handleChange = (e) => {
    const { name, value } = e.target;
    dispatch({
      type: 'SET_FIELD',
      field: name,
      value
    });

    // 验证逻辑
    if (name === 'email' && !value.includes('@')) {
      dispatch({
        type: 'SET_ERROR',
        field: 'email',
        error: '请输入有效的邮箱地址'
      });
    }
  };

  return (
    <form>
      <div>
        <input
          name="username"
          value={state.username}
          onChange={handleChange}
          placeholder="用户名"
        />
      </div>
      <div>
        <input
          name="email"
          value={state.email}
          onChange={handleChange}
          placeholder="邮箱"
        />
        {state.errors.email && (
          <span style={{ color: 'red' }}>{state.errors.email}</span>
        )}
      </div>
      <div>
        <input
          name="password"
          type="password"
          value={state.password}
          onChange={handleChange}
          placeholder="密码"
        />
      </div>
      <button type="button" onClick={() => dispatch({ type: 'RESET_FORM' })}>
        重置
      </button>
    </form>
  );
}

4. 使用 Immer 简化 Reducer 逻辑

Immer 允许我们以更直观的方式编写 reducer,无需手动处理不可变性。

4.1 安装 Immer

npm install immer

4.2 使用 Immer 重写 Todo 示例

import produce from 'immer';

const todoReducer = produce((draft, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      draft.todos.push({
        id: Date.now(),
        text: action.payload,
        completed: false
      });
      break;
    
    case 'TOGGLE_TODO':
      const todo = draft.todos.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
      break;
    
    case 'DELETE_TODO':
      const index = draft.todos.findIndex(t => t.id === action.payload);
      if (index !== -1) {
        draft.todos.splice(index, 1);
      }
      break;
  }
});

4.3 使用 Immer 简化复杂状态更新

具体参照:[https://immerjs.github.io/immer/zh-CN/example-setstate]

const complexReducer = produce((draft, action) => {
  switch (action.type) {
    case 'UPDATE_NESTED_STATE':
      draft.users[action.userId].preferences.theme = action.theme;
      break;
    
    case 'ADD_ITEM_TO_ARRAY':
      draft.items[action.categoryId].push(action.item);
      break;
    
    case 'UPDATE_MULTIPLE_FIELDS':
      Object.assign(draft.form, action.updates);
      break;
  }
});

function ComplexStateComponent() {
  const [state, dispatch] = useReducer(complexReducer, {
    users: {},
    items: {},
    form: {}
  });

  // 使用示例
  const updateTheme = (userId, theme) => {
    dispatch({
      type: 'UPDATE_NESTED_STATE',
      userId,
      theme
    });
  };

  const addItem = (categoryId, item) => {
    dispatch({
      type: 'ADD_ITEM_TO_ARRAY',
      categoryId,
      item
    });
  };
}

5. useReducer 使用场景

  1. 复杂的状态逻辑:当组件状态逻辑复杂,包含多个值时
  2. 相关状态更新:当多个状态更新紧密相关时
  3. 状态依赖其他状态:当状态更新依赖于其他状态值时
  4. 深层状态更新:当需要更新深层嵌套的状态时
  5. 状态更新需要集中管理:当需要在一个地方管理所有状态更新逻辑时

6. 最佳实践

  1. Action 类型常量化
const TODO_ACTIONS = {
  ADD: 'ADD_TODO',
  TOGGLE: 'TOGGLE_TODO',
  DELETE: 'DELETE_TODO'
};
  1. Action Creator 函数化
const createTodo = (text) => ({
  type: TODO_ACTIONS.ADD,
  payload: text
});
  1. 使用 TypeScript 定义类型
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

type TodoAction = 
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: number }
  | { type: 'DELETE_TODO'; payload: number };

const todoReducer = (state: Todo[], action: TodoAction): Todo[] => {
  // reducer 逻辑
};
  1. 合理拆分 Reducer
const rootReducer = (state, action) => {
  return {
    todos: todosReducer(state.todos, action),
    user: userReducer(state.user, action),
    ui: uiReducer(state.ui, action)
  };
};

通过使用 useReducer 和 Immer,我们可以更好地管理复杂的状态逻辑,同时保持代码的可读性和可维护性。Immer 特别适合处理深层嵌套的状态更新,让代码更简洁直观。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/955871.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode - 周赛432

目录 一、3417. 跳过交替单元格的之字形遍历二、3418. 机器人可以获得的最大金币数三、3419. 图的最大边权的最小值四、3420. 统计 K 次操作以内得到非递减子数组的数目 一、3417. 跳过交替单元格的之字形遍历 题目链接 本题是一道模拟题&#xff0c;第一行走0&#xff0c;2&…

ASP.NET Core - 配置系统之配置提供程序

ASP.NET Core - 配置系统之配置提供程序 3. 配置提供程序3.1 文件配置提供程序3.1.1 JSON配置提供程序3.1.2 XML配置提供程序3.1.3 INI配置提供程序 3.2 环境变量配置提供程序3.3 命令行配置提供程序3.4 内存配置提供程序3.5 配置加载顺序 3.6 默认配置来源 3. 配置提供程序 前…

探索与创作:2024年CSDN平台上的成长与突破

文章目录 我与CSDN的初次邂逅初学阶段的阅读CSDN&#xff1a;编程新手的避风港初学者的福音&#xff1a;细致入微的知识讲解考试复习神器&#xff1a;技术总结的“救命指南”曾经的自己&#xff1a;为何迟迟不迈出写博客的第一步兴趣萌芽&#xff1a;从“读”到“想写”的初体验…

CSS中样式继承+优先级

继承属性和非继承属性 一、定义及分类 1、继承属性是指在父元素上设置了这些属性后&#xff0c;子元素会自动继承这些属性的值&#xff0c;除非子元素显式地设置了不同的值。 常见的继承属性: 字体 font 系列文本text-align text-ident line-height letter-spacing颜色 col…

macOS 安装JDK17

文章目录 前言介绍新特性下载安装1.下载完成后打开downloads 双击进行安装2.配置环境变量3.测试快速切换JDK 小结 前言 近期找开源软件&#xff0c;发现很多都已经使用JDK17springboot3 了&#xff0c;之前的JDK8已经被替换下场&#xff0c;所以今天就在本机安装了JDK17&#…

ChatGPT大模型极简应用开发-CH1-初识 GPT-4 和 ChatGPT

文章目录 1.1 LLM 概述1.1.1 语言模型和NLP基础1.1.2 Transformer及在LLM中的作用1.1.3 解密 GPT 模型的标记化和预测步骤 1.2 GPT 模型简史&#xff1a;从 GPT-1 到 GPT-41.2.1 GPT11.2.2 GPT21.2.3 GPT-31.2.4 从 GPT-3 到 InstructGPT1.2.5 GPT-3.5、Codex 和 ChatGPT1.2.6 …

vector迭代器的使用以及迭代器失效

一、iterator的使用注意 begin与end 遵循左闭右开的原则&#xff0c;begin 指向vector的第一个元素&#xff0c;end 指向vector的最后一个元素的往下一个位置。 rbegin 与 rend rbegin指向最后一个元素的位置&#xff0c;rend指向第一个元素的往前一个位置。 二、vector的常…

【Linux】15.Linux进程概念(4)

文章目录 程序地址空间前景回顾C语言空间布局图&#xff1a;代码1代码2代码3代码4代码5代码6代码7 程序地址空间前景回顾 历史核心问题&#xff1a; pid_t id fork(); if(id 0) else if(id>0) 为什么一个id可以放两个值呢&#xff1f;之前没有仔细讲。 C语言空间布局图&am…

【无法下载github文件】虚拟机下ubuntu无法拉取github文件

修改hosts来进行解决。 步骤一&#xff1a;打开hosts文件 sudo vim /etc/hosts步骤二&#xff1a;查询 github.com的ip地址 https://sites.ipaddress.com/github.com/#ipinfo将github.com的ip地址添加到hosts文件末尾&#xff0c;如下所示。 140.82.114.3 github.com步骤三…

Android BitmapShader实现狙击瞄具十字交叉线准星,Kotlin

Android BitmapShader实现狙击瞄具十字交叉线准星&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.…

Android系统开发(十):标准协议和通讯的桥梁:探索蓝牙、NFC、WLAN 的工作原理

引言&#xff1a; 现代社会已经是信息互联的世界&#xff0c;各种设备之间的互联互通已经成为了生活的一部分。而在这个过程中&#xff0c;Android 设备与其他硬件之间的通信扮演着至关重要的角色。从蓝牙耳机到 WiFi 路由器&#xff0c;甚至与电话功能的互动&#xff0c;所有…

node中文名的js文件有问题

新版Node无法运行含有中文名的JS文件&#xff0c;具体表现在无报错无反应。如下图&#xff1a; 源码如下&#xff1a; 改成英文的JS文件&#xff0c;则正常&#xff0c;如下图&#xff1a;

BERT与CNN结合实现糖尿病相关医学问题多分类模型

完整源码项目包获取→点击文章末尾名片&#xff01; 使用HuggingFace开发的Transformers库&#xff0c;使用BERT模型实现中文文本分类&#xff08;二分类或多分类&#xff09; 首先直接利用transformer.models.bert.BertForSequenceClassification()实现文本分类 然后手动实现B…

openharmony应用开发快速入门

开发准备 本文档适用于OpenHarmony应用开发的初学者。通过构建一个简单的具有页面跳转/返回功能的应用&#xff08;如下图所示&#xff09;&#xff0c;快速了解工程目录的主要文件&#xff0c;熟悉OpenHarmony应用开发流程。 在开始之前&#xff0c;您需要了解有关OpenHarmon…

RabbitMQ---TTL与死信

&#xff08;一&#xff09;TTL 1.TTL概念 TTL又叫过期时间 RabbitMQ可以对队列和消息设置TTL&#xff0c;当消息到达过期时间还没有被消费时就会自动删除 注&#xff1a;这里我们说的对队列设置TTL,是对队列上的消息设置TTL并不是对队列本身&#xff0c;不是说队列过期时间…

51.WPF应用加图标指南 C#例子 WPF例子

完整步骤&#xff1a; 先使用文心一言生成一个图标如左边使用Windows图片编辑器编辑&#xff0c;去除背景使用正方形&#xff0c;放大图片使图标铺满图片使用格式工程转换为ico格式&#xff0c;分辨率为最大 在资源管理器中右键项目添加ico类型图片到项目里图片属性设置为始终…

多语言插件i18n Ally的使用

先展示一下效果 1.第一步首先在vscode下载插件 2.第二步在 setting.json 里面配置 要区分文件是js&#xff0c;ts或json结尾 以zh.ts和en.ts结尾的用这个 { "i18n-ally.localesPaths": ["src/locales"],"i18n-ally.keystyle": "nested"…

蓝桥杯备考:堆和priority queue(优先级队列)

堆的定义 heap堆是一种特殊的完全二叉树&#xff0c;对于树中的每个结点&#xff0c;如果该结点的权值大于等于孩子结点的权值&#xff0c;就称它为大根堆&#xff0c;小于等于就叫小根堆&#xff0c;如果是大根堆&#xff0c;每个子树也是符合大根堆的特征的&#xff0c;如果是…

【人工智能】:搭建本地AI服务——Ollama、LobeChat和Go语言的全方位实践指南

前言 随着自然语言处理&#xff08;NLP&#xff09;技术的快速发展&#xff0c;越来越多的企业和个人开发者寻求在本地环境中运行大型语言模型&#xff08;LLM&#xff09;&#xff0c;以确保数据隐私和提高响应速度。Ollama 作为一个强大的本地运行框架&#xff0c;支持多种先…

Java锁 从乐观锁和悲观锁开始讲 面试复盘

目录 面试复盘 Java 中的锁 大全 悲观锁 专业解释 自我理解 乐观锁 专业解释 自我理解 悲观锁的调用 乐观锁的调用 synchronized和 ReentrantLock的区别 相同点 区别 详细对比 总结 面试复盘 Java 中的锁 大全 悲观锁 专业解释 适合写操作多的场景 先加锁可以…