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.pswp.cn/news/39567.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/39567.shtml
英文地址,請注明出處:http://en.pswp.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網絡江湖初探:物理層的奧秘與傳承(物理層上篇-基礎與本質)

〇、引言 在這個數字時代,計算機網絡如同廣袤的江湖,數據在其中暢游,信息傳遞成為了生活的常態。然而,在這個充滿虛擬奇觀的網絡江湖中,隱藏著一個不容忽視的存在,那就是物理層,這個江湖的基石。就如同江湖中的土地一樣,物理層作為計算機網絡的基礎,承載著數據的最初轉…