useReducer的使用以及与useState、useImmerReducer的对比使用

news2024/9/29 1:28:06

前言

对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会影响代码的可读性。这种情况,可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数就是reducer。

使用

useReducer(reducer, initialArg, init?)

参数

  • reducer:用于更新 state 的纯函数。参数为 state 和 action,返回值是更新后的state。state 与 action 可以是任意合法值。
  • initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
  • 可选参数 init:用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg

返回值

useReducer 返回一个由两个值组成的数组:

  1. 当前的 state。初次渲染时,它是 init(initialArg)initialArg (如果没有 init 函数)。
  2. dispatch 函数。用于更新 state 并触发组件的重新渲染。

注意事项

  • useReducer 是一个 Hook,所以只能在 组件的顶层作用域 或自定义 Hook 中调用,而不能在循环或条件语句中调用。如果你有这种需求,可以创建一个新的组件,并把 state 移入其中。
  • 严格模式下 React 会 调用两次 reducer 和初始化函数,这可以帮助你发现意外的副作用。这只是开发模式下的行为,并不会影响生产环境。只要 reducer 和初始化函数是纯函数(理应如此)就不会改变你的逻辑。其中一个调用结果会被忽略。

dispatch 函数

useReducer 返回的 dispatch 函数允许你更新 state 并触发组件的重新渲染。它需要传入一个 action 作为参数:

const [state, dispatch] = useReducer(reducer, { age: 42 });



function handleClick() {

  dispatch({ type: 'incremented_age' });

  // ...

React 会调用 reducer 函数以更新 state,reducer 函数的参数为当前的 state 与传递的 action。

参数
  • action:用户执行的操作。可以是任意类型的值。通常来说 action 是一个对象,其中 type 属性标识类型,其它属性携带额外信息。
返回值

dispatch 函数没有返回值。

注意
  • dispatch 函数 是为下一次渲染而更新 state。因此在调用 dispatch 函数后读取 state 并不会拿到更新后的值,也就是说只能获取到调用前的值。
  • 如果你提供的新值与当前的 state 相同(使用 Object.is 比较),React 会 跳过组件和子组件的重新渲染,这是一种优化手段。虽然在跳过重新渲染前 React 可能会调用你的组件,但是这不应该影响你的代码。
  • React 会批量更新 state。state 会在 所有事件函数执行完毕 并且已经调用过它的 set 函数后进行更新,这可以防止在一个事件中多次进行重新渲染。如果在访问 DOM 等极少数情况下需要强制 React 提前更新,可以使用 flushSync

创建Reducer

const [state, dispatch] = useReducer(reducer, initdata)

useReducer 返回一个由两个值组成的数组:

  1. 当前的 state,首次渲染时为你提供的 初始值
  2. dispatch 函数,让你可以根据交互修改 state

为了更新dom,使用一个表示用户操作的 action 来调用 dispatch 函数:

function handleClick() {
  dispatch({ type: 'incremented_age' })
}

React 会把当前的 state 和这个 action 一起作为参数传给 reducer 函数,然后 reducer 计算并返回新的 state,最后 React 保存新的 state,并使用它渲染组件和更新 UI。

实现reducer函数

function reducer(state, action) {
  // ...
}

处理 state 的逻辑一般会使用 switch 语句来完成。在 switch 语句中通过匹配 case 条件来计算并返回相应的 state。

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

action 可以是任意类型,不过通常至少是一个存在 type 属性的对象。也就是说它需要携带计算新的 state 值所必须的数据。

function Form() {
  const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }
  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    });
  }
  // ...

action 的 type 依赖于组件的实际情况。即使它会导致数据的多次更新,每个 action 都只描述一次交互。state 的类型也是任意的,不过一般会使用对象或数组

注意

state 是只读的。即使是对象或数组也不要尝试修改它。

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // 🚩 不要像下面这样修改一个对象类型的 state:
      state.age = state.age + 1;
      return state;
    }
 	}
}

正确的做法是返回新的对象:

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      // ✅ 正确的做法是返回新的对象
      return {
        ...state,
        age: state.age + 1
      };
    }
  }
}

如果想直接修改数据,可以使用Immer,如下实例中说明了Immer的使用。

实例

点击添加,新增一行

点击编辑,字符拼接"new"

点击删除,删除行

在这里插入图片描述

使用 useState 实现

使用 useState 实现,可以发现代码量偏多,如果单文件出现多种操作,代码可读性变差。

import { useState } from "react"

function App(){
  const [list,setList] = useState([
    {id: 1, text: 'aaa'},
    {id: 2, text: 'bbb'},
    {id: 3, text: 'ccc'}
  ])
  const addHandler = () => {
    setList([...list,{id:4,text:"ddd"}])
  }
  const editHandler = (id) => {
    setList(list.map(item=>{
      if(item.id === id){
        return {...item,text:"new" + item.text}
      }else{
        return item
      }
    }))
  }
  const delHandler = (id) => {
    setList(list.filter(item=>{
      if(item.id === id){
        return false
      }else{
        return true
      }
    }))
  }
  return (
  	<div>
      <button onClick={() => addHandler()}>添加</button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button onClick={() => editHandler(item.id)}>编辑</button>
              <button onClick={() => delHandler(item.id)}>删除</button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

使用 useReducer 实现

使用 useReducer 进行统一管理,可以发现代码可读性提高了。

import { useReducer } from "react"

// 由着reducer函数完成外部逻辑的统一处理
function listReducer(state, action) {
  switch (action.type) {
    case "add":
      return [...state, { id: +new Date(), text: "ddd" }]
    case "edit":
      return state.map((item) => {
        if (item.id === action.id) {
          return { ...item, text: "new" + item.text }
        } else {
          return item
        }
      })
    case "remove":
      return state.filter((item) => {
        if (item.id === action.id) {
          return false
        } else {
          return true
        }
      })
  }
}

function App(){
  const [list,listDispatch] = useReducer(listReducer,[
    {id: 1, text: 'aaa'},
    {id: 2, text: 'bbb'},
    {id: 3, text: 'ccc'}
  ])
  return (
  	<div>
      <button onClick={() => listDispatch({ type: "add" })}>添加</button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button
                onClick={() => listDispatch({ type: "edit", id: item.id })}
              >
                编辑
              </button>
              <button
                onClick={() => listDispatch({ type: "remove", id: item.id })}
              >
                删除
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

使用 useImmerReducer 实现

使用 useImmerReducer,可以更加方便的处理复杂数据。

import { useImmerReducer } from "use-immer"

// 由着reducer函数完成外部逻辑的统一处理
function listReducer(draft, action) {
  switch (action.type) {
    case "add":
      draft.push({ id: +new Date(), text: "ddd" })
      break
    case "edit":
      const value = draft.find((item) => item.id === action.id)
      value.text = "new" + value.text
      break
    case "remove":
      const index = draft.findIndex((item) => item.id === action.id)
      draft.splice(index, 1)
      break
  }
}

function App(){
  const [list,listDispatch] = useImmerReducer(listReducer,[
    {id: 1, text: 'aaa'},
    {id: 2, text: 'bbb'},
    {id: 3, text: 'ccc'}
  ])
  return (
  	<div>
      <button onClick={() => listDispatch({ type: "add" })}>添加</button>
      <ul>
        {list.map((item) => {
          return (
            <li key={item.id}>
              {item.text}
              <button
                onClick={() => listDispatch({ type: "edit", id: item.id })}
              >
                编辑
              </button>
              <button
                onClick={() => listDispatch({ type: "remove", id: item.id })}
              >
                删除
              </button>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

总结

当组件里有多个状态更新逻辑,并且有些是有一定关联性的,那么多个useState会降低代码可读性,为解决这个问题,我们可以将多个状态更新逻辑整合到一个外部函数,这个函数就是reducer。reducer的state是只读的,不可修改,如果想直接操作可以使用Immer。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电力通信与泛在电力物联网技术的应用与发展-安科瑞黄安南

摘要&#xff1a;随着我国社会经济的快速发展&#xff0c;我国科技实力得到了非常大的提升&#xff0c;当前互联网通信技术在社会中得到了广泛的应用。随着电力通信技术的快速发展与更新&#xff0c;泛在电力物联网建设成为电力通讯发展的重要方向。本文已泛在电力物联网系统为…

化工园区数字孪生可视化管控平台,赋予园区安全环保智慧发展

化工行业作为国民经济的支柱和工业发展的引擎&#xff0c;对安全生产、环保节能、应急管控有着很高的要求。目前国内外化工园区面临安全和环保两大压力。为有效解决这两大难题&#xff0c;巨蟹数科综合运用物联网、数字孪生等新一代信息技术&#xff0c;建设了数字孪生园区智慧…

Docker下安装MSSQL并使用Navicat远程连接(备忘录)

Docker下安装MSSQL并使用Navicat远程连接 一. Docker下安装MSSQL备忘录一、安装SQL Server1、从 Microsoft 容器注册表中请求 SQL Server 2022 (16.x) Linux 容器映像:注意:2、运行这个cu5的版本下表对前一个 docker run 示例中的参数进行了说明:3、看这个MSSQL运行没有?用…

JAVA基础——编译器报告的错误信息总结

1. Invalid character&#xff08;无效字符&#xff09; 中英文符号错误 2. 数组的常见异常 数组越界异常ArrayIndexOutOfBoundsException: 在使用索引访问数组的元素时超出了数组的索引范围0~length-1。 空指针异常NullPointerException&#xff1a; 在使用变量引用一个数…

CSS - 常用属性和布局方式

目录 前言 一、常用属性 1.1、字体相关 1.2、文本相关 1.3、背景相关 1.3.1、背景颜色 1.3.2、背景图片 1.4、圆角边框 二、常用布局相关 2.1、display 2.2、盒子模型 2.2.1、基本概念 2.2.2、border 边框 2.2.3、padding 内边距 2.2.4、margin 外边距 2.3、弹…

【Android】MQTT

目录 MQTT 协议简介应用场景优点缺点 部署服务端下载安装包启动服务器 搭建客户端下载SDK添加依赖配置MQTT服务和权限建立连接订阅主题发布消息取消订阅断开连接 MQTT客户端工具最终效果实现传感器数据采集与监测功能思路 MQTT 协议 简介 MQTT&#xff08;Message Queuing Te…

【目标跟踪】多目标跟踪测距

文章目录 前言python代码&#xff08;带注释&#xff09;main.pysort.pykalman.pydistance.py 结语 前言 先放效果图。目标框内左上角&#xff0c;显示的是目标距离相机的纵向距离。目标横向距离、速度已求出&#xff0c;没在图片展示。这里不仅仅实现对目标检测框的跟踪&#…

【Django restframework】django跨域问题,解决PUT/PATCH/DELETE用ajax请求无法提交数据的问题

【Django restframework】django跨域问题&#xff0c;解决PUT/PATCH/DELETE用ajax请求无法提交数据的问题 1 问题描述&#xff1a; 我用restframework(ModelSerializerGenericApiView)开发了一组符合RestFul接口标准的接口&#xff0c;这意味着它将支持客户端发来的GET、POST、…

矢量图形编辑软件 illustrator 2023 mac 中文软件特点

illustrator 2023 mac是一款矢量图形编辑软件&#xff0c;用于创建和编辑排版、图标、标志、插图和其他类型的矢量图形。 illustrator 2023 mac软件特点 矢量图形&#xff1a;illustrator创建的图形是矢量图形&#xff0c;可以无限放大而不失真&#xff0c;这与像素图形编辑软…

【接口测试】Jmeter接口实战-Dubbo接口+造10W数据测试(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Windows环境通…

Harmony 个人中心(页面交互、跳转、导航、容器组件)

个人中心 前言正文一、创建工程二、登录① 更换启动页面② 拓展修饰符③ 页面跳转④ 等待进度条 三、导航栏四、首页① 轮播图② 网格列表 五、我的① 带参数跳转 六、源码 前言 今天是1024&#xff0c;祝各位程序员们&#xff0c;钱多事少离家近&#xff0c;不秃也强bug黄。在…

Plooks大型视频在线一起看网站源码

在前段时间&#xff0c;因为想和异地的朋友一起看电影&#xff0c;但是发现有电影的地方没有一起看功能&#xff0c;有一起看功能的视频网站没有电影&#xff0c;所以就想自己做一个一起看网站&#xff0c;于是就有了Plooks。 Plooks是一个完整的视频网站&#xff0c;其中包括…

linux驱动的IO 模型(高级字符设备一)

IO 是英文 Input 和 Output 的首字母&#xff0c;代表了输入和输出。操作系统&#xff08;Linux&#xff09;负责对计算机的资源进行管理和对进程进行调度&#xff0c;应用程序运行在操作系统上&#xff0c;处于用户空间。应用程序不能直接对硬件进行操作&#xff0c;只能通过操…

uniapp把文件中的内复制到另一个文件中

使用的是Html 5的plus.io.resolveLocalFileSystemURL方法&#xff0c;文档&#xff1a;HTML5 API Reference var soursePath file:///storage/emulated/0/a/;//用于读取var removePath file:///storage/emulated/0/w/;//用于移除w这个文件夹var targetPath file:///storage/…

03初始Docker

一、初始Docker 1.什么是Docker 问题 ①大型项目组件复杂&#xff0c;运行环境复杂&#xff0c;部署时依赖复杂&#xff0c;出现兼容性问题。 ②开发&#xff0c;测试&#xff0c;生产环境有差异。不同的环境操作系统不同 解决 ①Docket将应用、依赖、函数库、配置一起打…

ChatGPT AIGC 完成 Excel多条件求和操作

企业产品销售额是企业在一定时间内通过销售其产品获取的收入总额。 这个指标通常用于衡量企业的销售能力、市场占有率以及企业的健康度。企业产品销售额具体的计算方法是将企业在销售商品或服务时所取得的所有收入加总而得出。 在这个过程中,通常会考虑到可能存在的退货、折…

基于nodejs+vue备忘记账系统mysql

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

上传和下载文件到google drive/Local pc

1 上传 参考&#xff1a;使用 Python 将文件上传到 Google 云端硬盘_迹忆客 Upload file to google drive using Python - CodeSpeedy (没起作用&#xff0c;但可以参考一下) 第 1 步&#xff1a;Google API Playground 我们可以通过搜索 Google 找到更多关于 Google API Pla…

Linux 开机启动一条PHP命令

当你开机的时候要自动的启动一条PHP命令场景&#xff1a;比如webman 你需要手动启动项目进程 你可以这样操作 流程&#xff1a; 1、准备好你要执行的命令 2、将命令写入一个服务文件 3、开机自启这个服务 实例&#xff1a; 1、比如这个命令 /usr/local/php/bin/php /ho…

Class类文件中的“咖啡宝贝”

Class文件是一组以8个字节为基础单位的二进制流&#xff0c;各个数据项目严格按照顺序紧凑地排列在文件之中&#xff0c;中间没有添加任何分隔符&#xff0c;整个Class文件中存储的内容几乎全部是程序运行的必要数据&#xff0c;没有空隙存在。 字节码&#xff08;Byte Code&am…