对useReducer的理解

2023-11-18

useReducer是React提供的一个高级Hook,它不像useEffect、useState、useRef等必须hook一样,没有它我们也可以正常完成需求的开发,但useReducer可以使我们的代码具有更好的可读性、可维护性、可预测性。

什么是reducer

reducer的概念是伴随着Redux的出现逐渐在JavaScript中流行起来。但我们并不需要学习Redux去了解Reducer。

简单来说 reducer是一个函数(state, action) => newState:接收当前应用的state和触发的动作action,计算并返回最新的state。下面是一段伪代码:

    // 举个栗子 计算器reducer,根据state(当前状态)和action(触发的动作加、减)参数,计算返回newState
    function countReducer(state, action) {
        switch(action.type) {
            case 'add':
                return state + 1;
            case 'sub':
                return state - 1;
            default: 
                return state;
        }
    }

上面例子:state是一个number类型的数值,reducer根据action的类型(加、减)对应的修改state,并返回最终的state。为了刚接触到reducer概念的小伙伴更容易理解,可以将state改为count,但请始终牢记count仍然是state

    function countReducer(count, action) {
        switch(action.type) {
            case 'add':
                return count + 1;
            case 'sub':
                return count - 1;
            default: 
                return count;
        }
    }

reducer 的幂等性

从上面的示例可以看到reducer本质是一个纯函数,没有任何UI和副作用。这意味着相同的输入(state、action),reducer函数无论执行多少遍始终会返回相同的输出(newState)。因此通过reducer函数很容易推测state的变化,并且也更加容易单元测试。

    expect(countReducer(1, { type: 'add' })).equal(2); // 成功
    expect(countReducer(1, { type: 'add' })).equal(2); // 成功
    expect(countReducer(1, { type: 'sub' })).equal(0); // 成功

state 和 newState 的理解

state是当前应用状态对象,可以理解就是我们熟知的React里面的state。

在上面的例子中state是一个基础数据类型,但很多时候state可能会是一个复杂的JavaScript对象,如上例中count有可能只是 state中的一个属性。针对这种场景我们可以使用ES6的结构赋值:

    // 返回一个 newState (newObject)
    function countReducer(state, action) {
        switch(action.type) {
            case 'add':
                return { ...state, count: state.count + 1; }
            case 'sub':
                return { ...state, count: state.count - 1; }
            default: 
                return count;
        }
    }

关于上面这段代码有两个重要的点需要我们记住:

  1. reducer处理的state对象必须是immutable,这意味着永远不要直接修改参数中的state对象,reducer函数应该每次都返回一个新的state object
  2. 既然reducer要求每次都返回一个新的对象,我们可以使用ES6中的解构赋值方式去创建一个新对象,并复写我们需要改变的state属性,如上例。

看上去很完美,但如果我们的state是多层嵌套,解构赋值实现就非常复杂:

    function bookReducer(state, action) {
        switch(action.type) {
            // 添加一本书
            case 'addBook':
                return {
                    ...state,
                    books: {
                        ...state.books,
                        [bookId]: book,
                    }
                };
            case 'sub':
                // ....
            default: 
                return state;
        }
    }

对于这种复杂state的场景推荐使用immer等immutable库解决。

state为什么需要immutable?

  • reducer的幂等性

我们上文提到过reducer需要保持幂等性,更加可预测、可测试。如果每次返回同一个state,就无法保证无论执行多少次都是相同的结果

  • React中的state比较方案

React在比较oldStatenewState的时候是使用Object.is函数,如果是同一个对象则不会出发组件的rerender。 可以参考官方文档bailing-out-of-a-dispatch

action 理解

action:用来表示触发的行为。

  1. 用type来表示具体的行为类型(登录、登出、添加用户、删除用户等)
  2. 用payload携带的数据(如增加书籍,可以携带具体的book信息),我们用上面addBook的action为例:
    const action = {
        type: 'addBook',
        payload: {
            book: {
                bookId,
                bookName,
                author,
            }
        }
    }
    function bookReducer(state, action) {
        switch(action.type) {
            // 添加一本书
            case 'addBook':
                const { book } = action.payload;
                return {
                    ...state,
                    books: {
                        ...state.books,
                        [book.bookId]: book,
                    }
                };
            case 'sub':
                // ....
            default: 
                return state;
        }
    }

总结

至此基本介绍完了reducer相关的内容,简单总结一下:reducer是一个利用action提供的信息,将state从A转换到B的一个纯函数,具有一下几个特点:

  • 语法:(state, action) => newState
  • Immutable:每次都返回一个newState, 永远不要直接修改state对象
  • Action:一个常规的Action对象通常有type和payload(可选)组成
    • type: 本次操作的类型,也是 reducer 条件判断的依据
    • payload: 提供操作附带的数据信息
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

对useReducer的理解 的相关文章

  • 由于 apollo-client 未定义,无法解构 GraphQL 查询?

    我正在寻找调试与错误消息相关的问题 未捕获的类型错误 无法解构 0 apollo client WEBPACK IMPORTED MODULE 4 useQuery 因为它未定义 Context 我正在为我的 React js 项目设置后端
  • 渲染货币和符号并与来自不同单元格的数据相结合

    我正在使用最新的 jQuery DataTables v1 10 7 我正在尝试将数字解析为以下格式 239 90 USD 我可以使用此命令使货币正常工作 columns data Price render fn dataTable ren
  • 以编程方式填写reactjs表单

    我正在编写一个用户脚本 但无法填写由reactjs制作的表单 我的代码 document querySelector id username value email protected cdn cgi l email protection
  • 如何在react-bootstrap中禁用表单提交的

    在下面的代码片段中 我有许多文本类型的输入表单 如果用户点击 我似乎会得到相同的合成事件 就像他们按下提交按钮一样 我想忽略作为表单提交 只允许一个人按下 提交 按钮 我删除了一些表单组以减少示例 在所有情况下 按钮或 ENTER 键 e
  • 如何使用javascript将大图像转换为十六进制?

    如果我尝试将图像转换为十六进制 无论我使用哪个函数 我都会收到此错误消息 该图像的大小为 7 MB 19812 毫秒 清理 1401 2 1455 0 gt 1401 2 1455 0 MB 9 9 0 ms 自上次 GC 以来 8 3 m
  • 如何将内联 JavaScript 与 Express/Node.js 中动态生成的内容分开?

    对于具有几年 Web 开发经验但没有找到答案的人来说 这是一个有点菜鸟的问题程序员堆栈交换 or Google 我决定在这里问一下 我在用Express网络框架Node js 但这个问题并不特定于任何 Web 框架或编程语言 以下是从数据库
  • 在打字稿中导入 json

    我是 typescript 的新手 在我的项目中 我们使用 typescript2 在我的要求之一中 我需要导入 json 文件 所以我创建了 d ts 文件如下 test d ts declare module json const va
  • Bootstrap按钮加载+Ajax

    我正在使用 Twitter Bootstrap 的按钮加载状态 http twitter github com bootstrap javascript html buttons http twitter github com bootst
  • 有没有办法使用 Rspec/Capybara/Selenium 将 javascript console.errors 打印到终端?

    当我运行 rspec 时 是否可以让 capybara selenium 向 rspec 报告任何 javascript console errors 和其他异常 我有一大堆测试失败 但当我手动测试它时 我的应用程序正在运行 如果不知道仅在
  • 调整图像大小并将画布旋转 90 度

    这里有很多关于在 js 上使用画布旋转图像的主题 我阅读了其中的大部分内容 但无法找到解决我的问题的方法 我正在接收任何分辨率的图像 来自上传组件 我将其大小调整为 1024x768 如下所示 var canvas document cre
  • Javascript split 不是一个函数

    嘿朋友们 我正在使用 javascript sdk 通过 jQuery facebook 多朋友选择器在用户朋友墙上发布信息 但是我收到此错误friendId split 不是函数 这是我的代码 function recommendToFr
  • 有没有办法在 onclick 触发时禁用 iPad/iPhone 上的闪烁/闪烁?

    所以我有一个有 onclick 事件的区域 在常规浏览器上单击时 它不会显示任何视觉变化 但在 iPad iPhone 上单击时 它会闪烁 闪烁 有什么办法可以阻止它在 iPad iPhone 上执行此操作吗 这是一个与我正在做的类似的示例
  • 可以设置标题样式吗? (并且使用CSS或js?)[重复]

    这个问题在这里已经有答案了 我想知道是否可以设计一个title a href title This is a title Hello a 样式问题有两个方面 文本格式 编码 我猜这是可能的 所以在问题中这样做 工具提示样式 你能把它弄大一点
  • 从数据库检查数据的异步解决方案各种循环子句

    我想要做的是异步检查数据库并从中获取结果 在我的应用程序中我试图实现Asynchronously将此步骤解决为 从数据库中检查手机号码JsonArray循环子句的种类 Create JsonArray从结果 打印创建的数组 我学到了足够多的
  • 在移动设备上滚动

    这个问题更多的是一个建议研究 我确实希望它对其他人有帮助 并且它不会关闭 因为我不太确定在哪里寻求有关此事的建议 在过去的 6 个月里 我一直在进行移动开发 我有机会处理各种设备上的各种情况和错误 最麻烦的是滚动问题 当涉及到在网站的多个区
  • 使用 Vue 的多模式组件

    我在 Vue 中实现动态模式组件时遇到问题 A common approach I follow to display a set of data fetched from the db is I dump each of the rows
  • 对于只触及我的工作表的 Google 表格脚本,收到“此应用程序未经验证”

    我正在编写一个 Google Sheets 脚本 我只想访问与 gs 文件关联的同一电子表格中的数据 似乎我应该有权在自己的电子表格中运行脚本 但是每当我运行一个函数时 我都会得到一个This app isn t verified信息 我该
  • 在 Shopify 商店中嵌入 Vue 组件

    在产品页面中 我尝试显示自定义 Vue 组件 为简洁起见 该组件根据给定的产品 ID 显示 Firebase 数据库中的一些信息 我最初尝试将其制作为 Shopify 应用程序 以便我可以访问他们的 API 我实现了 OAuth 并且可以检
  • 如何隐藏/禁用 Highcharts.js 中的图例框?

    我想问是否可以使用 HighCharts js 库隐藏图表中的所有图例框 var chart object chart renderTo render to type graph type colors graph colors title
  • JavaScript 相对路径

    在第一个 html 文件中 我使用了一个变量类别链接 var categoryLinks Career prospects http localhost Landa DirectManagers 511 HelenaChechik Dim0

随机推荐

  • 三分钟,教你3种前端埋点方式!

    大厂技术 高级前端 Node进阶 点击上方 程序员成长指北 关注公众号 回复1 加入高级Node交流群 只有了解用户 我们才能服务好用户 而最接近用户的我们 自然要承担起更多的责任 那么在一个企业中 我们要如何去了解用户呢 最直接有效的方式
  • 警告: Unable to find required classes (javax.activation.DataHandler and javax.mail

    在调试Axis1 4访问WebService服务时 出现以下警告 警告 Unable to find required classes javax activation DataHandler and javax mail internet
  • sip线路对接_线路调度一二三

    使用呼叫中心系统 在进行外呼业务时 坐席的呼出呼叫 最终需要通过各种线路进行呼出 根据线路类别不同 目前常见的有数字中继线 SIP中继对接线路 IMS接入线路 FXO大号模拟线 卡线 API对接线路 除自有线路外 根据供应商类型 主要有各地
  • 【Linux中shell脚本】Linux中shell脚本回车换行问题

    背景 windows下编辑shell脚本 报错 syntax error near unexpected token r 问题产生原因 因为windos与linux换行符不同导致 notepad 中 视图 gt 显示符号 gt 显示所有符号
  • Qt技术重绘QtableView方法

    在项目过程中需要在列表中用图形显示磁盘的使用情况 这用Qt自身的QTableview控件不能完全实现 需要用委托进行重绘 创建一个委托类继承QItemDelegate类 然后实现void paint QPainter painter con
  • 研报精选230410

    目录 行业230410西南证券 医药行业2023年4月投资月报 看好创新药和中药行情 行业230410国信证券 汽车行业4月投资策略 3月新能源乘用车批发销量预计同比增长32 持续关注板块年报季报行情 行业230410西南证券 医药行业周报
  • java------int=20是存在堆里还是栈里

    如果方法里的局部变量 就会存在栈帧里的局部变量表中 如果是成员变量则存在于堆中 它属于int类的一个实例 存放在堆中开辟的空间
  • 【Mo 人工智能技术博客】图卷积网络概述及其在论文分类上的应用

    近年来 深度学习在计算机视觉 自然语言处理等领域大放异彩 这些领域所面对的数据都是结构化的 如图像 音频 文本等 它们内部都有明确的排列规则 结构化的数据由于具有这些确定的规则而方便处理 但是在现实生活中 非结构化的关系数据才是主流 我们无
  • Ubuntu 18.04换国内源 中科大源 阿里源 163源 清华源

    国内有很多Ubuntu的镜像源 包括阿里的 网易的 还有很多教育网的源 比如 清华源 中科大源 我们这里以中科大的源为例讲解如何修改Ubuntu 18 04里面默认的源 编辑 etc apt sources list文件 在文件最前面添加以
  • 华为OD机试 - 跳格子1(Java)

    题目描述 小明和朋友玩跳格子游戏 有 n 个连续格子 每个格子有不同的分数 小朋友可以选择以任意格子起跳 但是不能跳连续的格子 也不能回头跳 给定一个代表每个格子得分的非负整数数组 计算能够得到的最高分数 输入描述 给定一个数列 如 1 2
  • 3D游戏设计大作业

    Unity大作业 粒子系统效果 一 前言 1 此篇文章记录了2022年第二学期中山大学软件工程3D游戏与编程的大作业 本次大作业可选择范围众多 最终我选择了粒子系统效果作为了本次大作业的主题 2 一个粒子系统可以模拟并渲染许多称为粒子的小图
  • CRC校验总结

    事实上网上很多CRC校验算法 在接收端进行CRC校验时 余数不为零 这往往是因为CRC校验算法本身是有问题的 但不妨碍我们进行校验 接收端可以对需要校验的字段带入CRC校验算法 计算得到校验值 并与发送数据中的校验值进行比较 如果两者相等
  • 生信技能树R语言学习

    一 数据类型和向量 1 数据类型 1 1 判断数据类型class 1 2 按Tab键自动补全 1 3 数据类型的判断和转换 1 is 族函数 判断 返回值为TRUE或FALSE is numeric 123 is character a i
  • 32位/64位WINDOWS驱动之-突破进程保护映射的方法进行跨进程读内存2

    32位 64位WINDOWS驱动之 突破进程保护映射的方法进行跨进程读内存2 一 在过保护读写筛选器中添加 读写驱动2 c 驱动层 代码如下 include
  • 黑马头条 热点文章实时计算、kafkaStream

    热点文章 实时计算 1 今日内容 1 1 定时计算与实时计算 1 2 今日内容 kafkaStream 什么是流式计算 kafkaStream概述 kafkaStream入门案例 Springboot集成kafkaStream 实时计算 用
  • cout 格式化输出

    将 cout 的 flag 保存到变量 以便修改后的恢复 ostream fmtflags old cout flag 无参将返回当前 flag 值 cout flag old 恢复到原先保存的值 将 bool 值以 literals 输出
  • 第六章课后习题及答案

    第六章习题答案 转载于 https www cnblogs com hhdn archive 2007 05 27 761356 html
  • Mysql字段设置默认值

    方法一 select 默认值 字段名 from 表名 方法二 SELECT 默认值 as 字段名 FROM 表名 以上方法 将默认值和表名互换 则可以产生给表名去别名的作用
  • 计算分组后的记录行数(count group)

    小弟有这样一个问题 想用hql计算分组之后的总记录行数 语句如 from aTable a left outer join bTable b group by a f1 b f1 现在要计算它的总记录数 select count from
  • 对useReducer的理解

    useReducer是React提供的一个高级Hook 它不像useEffect useState useRef等必须hook一样 没有它我们也可以正常完成需求的开发 但useReducer可以使我们的代码具有更好的可读性 可维护性 可预测