React 18 更新 state 中的对象

参考文章

更新 state 中的对象

state 中可以保存任意类型的 JavaScript 值,包括对象。但是,不应该直接修改存放在 React state 中的对象。相反,当想要更新一个对象时,需要创建一个新的对象(或者将其拷贝一份),然后将 state 更新为此对象。

什么是 mutation?

可以在 state 中存放任意类型的 JavaScript 值。

const [x, setX] = useState(0);

在 state 中存放数字、字符串和布尔值,这些类型的值在 JavaScript 中是不可变(immutable)的,这意味着它们不能被改变或是只读的。可以通过替换它们的值以触发一次重新渲染。

setX(5);

state x0 变为 5,但是数字 0 本身并没有发生改变。在 JavaScript 中,无法对内置的原始值,如数字、字符串和布尔值,进行任何更改。

现在考虑 state 中存放对象的情况:

const [position, setPosition] = useState({ x: 0, y: 0 });

从技术上来讲,可以改变对象自身的内容。当这样做时,就制造了一个 mutation

position.x = 5;

然而,虽然严格来说 React state 中存放的对象是可变的,但应该像处理数字、布尔值、字符串一样将它们视为不可变的。因此应该替换它们的值,而不是对它们进行修改。

将 state 视为只读的

换句话说,应该 把所有存放在 state 中的 JavaScript 对象都视为只读的

在下面的例子中,用一个存放在 state 中的对象来表示指针当前的位置。当在预览区触摸或移动光标时,红色的点本应移动。但是实际上红点仍停留在原处:

import { useState } from 'react';
export default function MovingDot() {const [position, setPosition] = useState({x: 0,y: 0});return (<divonPointerMove={e => {position.x = e.clientX;position.y = e.clientY;}}style={{position: 'relative',width: '100vw',height: '100vh',}}><div style={{position: 'absolute',backgroundColor: 'red',borderRadius: '50%',transform: `translate(${position.x}px, ${position.y}px)`,left: -10,top: -10,width: 20,height: 20,}} /></div>);
}

问题出在下面这段代码中。

onPointerMove={e => {position.x = e.clientX;position.y = e.clientY;
}}

这段代码直接修改了 上一次渲染中 分配给 position 的对象。但是因为并没有使用 state 的设置函数,React 并不知道对象已更改。所以 React 没有做出任何响应。虽然在一些情况下,直接修改 state 可能是有效的,但并不推荐这么做。应该把在渲染过程中可以访问到的 state 视为只读的。

在这种情况下,为了真正地 触发一次重新渲染,需要创建一个新对象并把它传递给 state 的设置函数

onPointerMove={e => {setPosition({x: e.clientX,y: e.clientY});
}}

通过使用 setPosition,在告诉 React:

  • 使用这个新的对象替换 position 的值
  • 然后再次渲染这个组件

现在可以看到,当在预览区触摸或移动光标时,红点会跟随着指针移动:

import { useState } from 'react';
export default function MovingDot() {const [position, setPosition] = useState({x: 0,y: 0});return (<divonPointerMove={e => {setPosition({x: e.clientX,y: e.clientY});}}style={{position: 'relative',width: '100vw',height: '100vh',}}><div style={{position: 'absolute',backgroundColor: 'red',borderRadius: '50%',transform: `translate(${position.x}px, ${position.y}px)`,left: -10,top: -10,width: 20,height: 20,}} /></div>);
}

使用展开语法复制对象

在之前的例子中,始终会根据当前指针的位置创建出一个新的 position 对象。但是通常,会希望把 现有 数据作为所创建的新对象的一部分。例如,可能只想要更新表单中的一个字段,其他的字段仍然使用之前的值。

下面的代码中,输入框并不会正常运行,因为 onChange 直接修改了 state :

import { useState } from 'react';export default function Form() {const [person, setPerson] = useState({firstName: 'Barbara',lastName: 'Hepworth',email: 'bhepworth@sculpture.com'});function handleFirstNameChange(e) {person.firstName = e.target.value;}function handleLastNameChange(e) {person.lastName = e.target.value;}function handleEmailChange(e) {person.email = e.target.value;}return (<><label>First name:<inputvalue={person.firstName}onChange={handleFirstNameChange}/></label><label>Last name:<inputvalue={person.lastName}onChange={handleLastNameChange}/></label><label>Email:<inputvalue={person.email}onChange={handleEmailChange}/></label><p>{person.firstName}{' '}{person.lastName}{' '}({person.email})</p></>);
}

例如,下面这行代码修改了上一次渲染中的 state:

person.firstName = e.target.value;

想要实现需求,最可靠的办法就是创建一个新的对象并将它传递给 setPerson。但是在这里,还需要 把当前的数据复制到新对象中,因为只改变了其中一个字段:

setPerson({firstName: e.target.value, // 从 input 中获取新的 first namelastName: person.lastName,email: person.email
});

可以使用 ... 对象展开 语法,这样就不需要单独复制每个属性。

setPerson({...person, // 复制上一个 person 中的所有字段firstName: e.target.value // 但是覆盖 firstName 字段 
});

现在表单可以正常运行了!

可以看到,并没有为每个输入框单独声明一个 state。对于大型表单,将所有数据都存放在同一个对象中是非常方便的——前提是能够正确地更新它!

import { useState } from 'react';export default function Form() {const [person, setPerson] = useState({firstName: 'Barbara',lastName: 'Hepworth',email: 'bhepworth@sculpture.com'});function handleFirstNameChange(e) {setPerson({...person,firstName: e.target.value});}function handleLastNameChange(e) {setPerson({...person,lastName: e.target.value});}function handleEmailChange(e) {setPerson({...person,email: e.target.value});}return (<><label>First name:<inputvalue={person.firstName}onChange={handleFirstNameChange}/></label><label>Last name:<inputvalue={person.lastName}onChange={handleLastNameChange}/></label><label>Email:<inputvalue={person.email}onChange={handleEmailChange}/></label><p>{person.firstName}{' '}{person.lastName}{' '}({person.email})</p></>);
}

请注意 ... 展开语法本质是“浅拷贝”——它只会复制一层。这使得它的执行速度很快,但是也意味着当想要更新一个嵌套属性时,必须得多次使用展开语法。

更新一个嵌套对象

考虑下面这种结构的嵌套对象:

const [person, setPerson] = useState({name: 'Niki de Saint Phalle',artwork: {title: 'Blue Nana',city: 'Hamburg',image: 'https://i.imgur.com/Sd1AgUOm.jpg',}
});

如果想要更新 person.artwork.city 的值,用 mutation 来实现的方法非常容易理解:

person.artwork.city = 'New Delhi';

但是在 React 中,需要将 state 视为不可变的!为了修改 city 的值,首先需要创建一个新的 artwork 对象(其中预先填充了上一个 artwork 对象中的数据),然后创建一个新的 person 对象,并使得其中的 artwork 属性指向新创建的 artwork 对象:

const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

或者,写成一个函数调用:

setPerson({...person, // 复制其它字段的数据 artwork: { // 替换 artwork 字段 ...person.artwork, // 复制之前 person.artwork 中的数据city: 'New Delhi' // 但是将 city 的值替换为 New Delhi!}
});

这虽然看起来有点冗长,但对于很多情况都能有效地解决问题:

import { useState } from 'react';export default function Form() {const [person, setPerson] = useState({name: 'Niki de Saint Phalle',artwork: {title: 'Blue Nana',city: 'Hamburg',image: 'https://i.imgur.com/Sd1AgUOm.jpg',}});function handleNameChange(e) {setPerson({...person,name: e.target.value});}function handleTitleChange(e) {setPerson({...person,artwork: {...person.artwork,title: e.target.value}});}function handleCityChange(e) {setPerson({...person,artwork: {...person.artwork,city: e.target.value}});}function handleImageChange(e) {setPerson({...person,artwork: {...person.artwork,image: e.target.value}});}return (<><label>Name:<inputvalue={person.name}onChange={handleNameChange}/></label><label>Title:<inputvalue={person.artwork.title}onChange={handleTitleChange}/></label><label>City:<inputvalue={person.artwork.city}onChange={handleCityChange}/></label><label>Image:<inputvalue={person.artwork.image}onChange={handleImageChange}/></label><p><i>{person.artwork.title}</i>{' by '}{person.name}<br />(located in {person.artwork.city})</p><img src={person.artwork.image} alt={person.artwork.title}/></>);
}

使用 Immer 编写简洁的更新逻辑

如果 state 有多层的嵌套,或许应该考虑 将其扁平化。但是,如果不想改变 state 的数据结构,可以使用 Immer 来实现嵌套展开的效果。Immer 是一个非常流行的库,它可以让你使用简便但可以直接修改的语法编写代码,并会帮你处理好复制的过程。通过使用 Immer,写出的代码看起来就像是“打破了规则”而直接修改了对象:

updatePerson(draft => {draft.artwork.city = 'Lagos';
});

但是不同于一般的 mutation,它并不会覆盖之前的 state!

尝试使用 Immer:

  1. 运行 npm install use-immer 添加 Immer 依赖
  2. import { useImmer } from 'use-immer' 替换掉 import { useState } from 'react'

下面我们把上面的例子用 Immer 实现一下:

import { useImmer } from 'use-immer';export default function Form() {const [person, updatePerson] = useImmer({name: 'Niki de Saint Phalle',artwork: {title: 'Blue Nana',city: 'Hamburg',image: 'https://i.imgur.com/Sd1AgUOm.jpg',}});function handleNameChange(e) {updatePerson(draft => {draft.name = e.target.value;});}function handleTitleChange(e) {updatePerson(draft => {draft.artwork.title = e.target.value;});}function handleCityChange(e) {updatePerson(draft => {draft.artwork.city = e.target.value;});}function handleImageChange(e) {updatePerson(draft => {draft.artwork.image = e.target.value;});}return (<><label>Name:<inputvalue={person.name}onChange={handleNameChange}/></label><label>Title:<inputvalue={person.artwork.title}onChange={handleTitleChange}/></label><label>City:<inputvalue={person.artwork.city}onChange={handleCityChange}/></label><label>Image:<inputvalue={person.artwork.image}onChange={handleImageChange}/></label><p><i>{person.artwork.title}</i>{' by '}{person.name}<br />(located in {person.artwork.city})</p><img src={person.artwork.image} alt={person.artwork.title}/></>);
}

package.json:

{"dependencies": {"immer": "1.7.3","react": "latest","react-dom": "latest","react-scripts": "latest","use-immer": "0.5.1"},"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test --env=jsdom","eject": "react-scripts eject"},"devDependencies": {}
}

可以看到,事件处理函数变得更简洁了。可以随意在一个组件中同时使用 useStateuseImmer。如果想要写出更简洁的更新处理函数,Immer 会是一个不错的选择,尤其是当 state 中有嵌套,并且复制对象会带来重复的代码时。

摘要

  • 将 React 中所有的 state 都视为不可直接修改的。
  • 当在 state 中存放对象时,直接修改对象并不会触发重渲染,并会改变前一次渲染“快照”中 state 的值。
  • 不要直接修改一个对象,而要为它创建一个 版本,并通过把 state 设置成这个新版本来触发重新渲染。
  • 可以使用这样的 {...obj, something: 'newValue'} 对象展开语法来创建对象的拷贝。
  • 对象的展开语法是浅层的:它的复制深度只有一层。
  • 想要更新嵌套对象,需要从更新的位置开始自底向上为每一层都创建新的拷贝。
  • 想要减少重复的拷贝代码,可以使用 Immer。

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

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

相关文章

图像去雨、去雪、去雾论文学习记录

All_in_One_Bad_Weather_Removal_Using_Architectural_Search 这篇论文发表于CVPR2020&#xff0c;提出一种可以应对多种恶劣天气的去噪模型&#xff0c;可以同时进行去雨、去雪、去雾操作。但该部分代码似乎没有开源。 提出的问题&#xff1a; 当下的模型只能针对一种恶劣天气…

【ARM 嵌入式 编译系列 4.1 -- GCC 编译属性 likely与unlikely 学习】

文章目录 GCC likely与unlikely 介绍linux 内核中的 likely/unlikely上篇文章:ARM 嵌入式 编译系列 4 – GCC 编译属性 __read_mostly 介绍 下篇文章: ARM 嵌入式 编译系列 4.2 – GCC 链接规范 extern “C“ 介绍 GCC likely与unlikely 介绍 likely 和 unlikely 是GCC编译器…

JDBC连接数据库(mysql)

准备jar包 官网下载即可&#xff0c;这里提供两个我下载过的jar包&#xff0c;供使用 链接&#xff1a;https://pan.baidu.com/s/1snikBD1kEBaaJnVktLvMdQ?pwdrwwq 提取码&#xff1a;rwwq eclipse导 jar包: 导入成功会有如下所示&#xff1a; ---------------------------…

个人开发中常见单词拼错错误纠正

个人开发中常见单词拼错错误纠正 前置说明参考地址后端开发相关前端开发相关客户端开发相关大数据/云计算相关工具或软件相关 前置说明 单词太多啦, 我这里只列表我个人见得比较多的, 我没见过就不列举了. 有错误或想补充的可以提交在原仓库提交Pull Request. &#x1f601; …

JavaScript面试题(二)

31、http 的理解 ? HTTP 协议是超文本传输协议&#xff0c;是客户端浏览器或其他程序“请求”与 Web 服务器响应之间的应用层通信协议。HTTPS主要是由HTTPSSL构建的可进行加密传输、身份认证的一种安全通信通道。 32、http 和 https 的区别 ? 1、https协议需要到ca申请证书…

基于DEM tif影像的插值平滑和tif纹理贴图构建方法

文章目录 基于CDT的无缝融合基于拓扑纠正的地上-地表的Bool运算融合 基于CDT的无缝融合 准备数据是一个10米分辨率的Tif影像&#xff0c;直接用于生成DEM会十分的不平滑。如下图所示&#xff0c;平滑前后的对比效果图差异&#xff1a; 基于ArcGIS的DEM平滑插值 等值线生成&…

Oracle增加列

在Oracle数据库中&#xff0c;使用ALTER TABLE语句可以很方便地为表增加新列。在进行操作时&#xff0c;需要谨慎考虑新列的数据类型、名称、默认值、约束等因素&#xff0c;以确保操作的安全性和可靠性。同时&#xff0c;也需要注意备份数据、避免在高峰期进行操作等注意事项 …

GPT内功心法:搜索思维到GPT思维的转换

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

Linux6.38 Kubernetes 集群存储

文章目录 计算机系统5G云计算第三章 LINUX Kubernetes 集群存储一、emptyDir存储卷2.hostPath存储卷3.nfs共享存储卷4.PVC 和 PV 计算机系统 5G云计算 第三章 LINUX Kubernetes 集群存储 容器磁盘上的文件的生命周期是短暂的&#xff0c;这就使得在容器中运行重要应用时会出…

编写 loading、加密解密 发布NPM依赖包,并实施落地使用

你的 Loading 开箱即可用的 loading&#xff0c; 说明&#xff1a;vue3-loading 是一个方便在 Vue 3 项目中使用的加载指示器组件的 npm 插件。它允许您轻松地在项目中添加加载动画&#xff0c;提升用户体验。 目录 你的 Loading&#x1f30d; 安装&#x1f6f9; 演示地址&…

C# WPF 无焦点自动获取USB 二维码扫码枪内容,包含中文

C# WPF 无焦点自动获取USB 二维码扫码枪内容&#xff0c;包含中文 前言项目背景 需要预知的知识实现方案第一步 安装键盘钩子第二步 获取输入的值第3 步 解决中文乱码问题分析解决思路工具函数 结束 前言 USB接口的扫码枪基本就相当于一个电脑外设&#xff0c;等同于一个快速输…

Oracle Data Redaction与Data Pump

如果表定义了Redaction Policy&#xff0c;导出时数据会脱敏吗&#xff1f;本文解答这个问题。 按照Oracle文档Advanced Security Guide第13章&#xff0c;13.6.5的Tutorial&#xff0c;假设表HR.jobs定义了Redaction Policy。 假设HR用户被授予了访问目录对象的权限&#xf…

Unity引擎使用InteriorCubeMap采样制作假室内效果

Unity引擎制作假室内效果 大家好&#xff0c;我是阿赵。   这次来介绍一种使用CubeMap做假室内效果的方式。这种技术名叫InteriorCubeMap&#xff0c;是UE引擎自带的节点效果。我这里是在Unity引擎里面的实现。 一、效果展示 这个假室内效果&#xff0c;要动态看才能看出效…

柏睿向量数据库Rapids VectorDB赋能企业级大模型构建及智能应用

ChatGPT的问世,在为沉寂已久的人工智能重新注入活力的同时,也把长期默默无闻的向量数据库推上舞台。今年4月以来,全球已有4家知名向量数据库公司先后获得融资,更加印证了向量数据库在AI大模型时代的价值。 什么是向量数据库? 在认识向量数据库前,先来了解一下最常见的关…

【业务功能篇62】Spring boot maven多模块打包时子模块报错问题

程序包 com.xxx.common.utils不存在或者xxx找不到符号 我们项目中一般都是会分成多个module模块&#xff0c;做到解耦&#xff0c;方便后续做微服务拆分模块&#xff0c;可以直接就每个模块进行打包拎出来执行部署这样就会有模块之间的调用&#xff0c;比如API模块会被Service…

【SpringBoot】SpringBoot获取不到用户真实IP怎么办

文章目录 前言问题原因解决方案修改Nginx配置文件SpringBoot代码实现 前言 项目部署后发现服务端无法获取到客户端真实的IP地址&#xff0c;这是怎么回事呢&#xff1f;给我都整懵逼了&#xff0c;经过短暂的思考&#xff0c;我发现了问题的真凶&#xff0c;那就是我们使用了N…

Vue基础

Vue基础 Vue应用 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title></title><!-- 开发环境版本 --><script src"https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </head&g…

vue所有UI库通用)tree-select 下拉多选(设置 maxTagPlaceholder 隐藏 tag 时显示的内容,支持鼠标悬浮展示更多

如果可以实现记得点赞分享&#xff0c;谢谢老铁&#xff5e; 1.需求描述 引用的下拉树形结构支持多选&#xff0c;限制选中tag的个数&#xff0c;且超过制定个数&#xff0c;鼠标悬浮展示更多已选中。 2.先看下效果图 3.实现思路 首先根据API文档&#xff0c;先设置maxTagC…

【Docker】Docker network之bridge、host、none、container以及自定义网络的详细讲解

&#x1f680;欢迎来到本文&#x1f680; &#x1f349;个人简介&#xff1a;陈童学哦&#xff0c;目前学习C/C、算法、Python、Java等方向&#xff0c;一个正在慢慢前行的普通人。 &#x1f3c0;系列专栏&#xff1a;陈童学的日记 &#x1f4a1;其他专栏&#xff1a;CSTL&…

TCP/IP网络江湖初探:物理层的奥秘与传承(物理层上篇-基础与本质)

〇、引言 在这个数字时代,计算机网络如同广袤的江湖,数据在其中畅游,信息传递成为了生活的常态。然而,在这个充满虚拟奇观的网络江湖中,隐藏着一个不容忽视的存在,那就是物理层,这个江湖的基石。就如同江湖中的土地一样,物理层作为计算机网络的基础,承载着数据的最初转…