iT邦幫忙

2025 iThome 鐵人賽

DAY 19
0
Modern Web

重溫 React 官方文件回到最初的起點系列 第 19

Day 19 - 更新 State 內的 Object Part 1

  • 分享至 

  • xImage
  •  

今天繼續介紹 state 的運用,前面的文章的 state 更新都是用 數字 number、字串 string、布林值 boolean 等的單一型態(type)值,今天則是要來介紹可以儲存不同型態資料的 object
今天的文章參考官方文件的:

讓 State 是唯讀(Read-Only)

當使用 object 的時候,我們是可以直接進行修改裡面資料的,像是:

const [playerInfo, setPlayerInfo] = useState({ lastName: "Wang", firstName: "Soto"})

playerInfo.lastName = "Juan"

但是在 React 裡面,會希望我們的 state 都是只能用來讀取的,要修改的話只能透過 setter 去取代原本的 state。可以看到文章裡的例子,是用 state position 來紀錄紅球在畫面的位置,當我們在畫面上移動的時候,預期的行為是紅球會跟著一起在上面移動。範例如下:

import { useState } from 'react';

export default function MovingDot() {
  const [position, setPosition] = useState({
    x: 0,
    y: 0
  });
  return (
    <div
      onPointerMove={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 這個 handler 裡面,是直接使用 position.x = e.clientX 去修改 state 資料,實際操作就會發現,不管我們怎麼移動,紅球都不會移動。這是因為雖然 state 的值有修改成功,但是 React 不知道是不是真的有修改,所以就沒辦法重新 render。要讓畫面重新更新,就要使用 setPosition 這個 state setter 才行。把原本 onPointerMove 裡的程式碼改成:

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

再重新操作一次就可以發現紅球可以跟著我們的滑鼠一起移動了。

使用展開語法(spread syntax)複製 objects

有些情況我們的 object 內容會有很多個值,但我們只需要更新其中一個,在寫 setter 的時候如果要把他全部填上去會很花時間並且讓程式碼很長,可以看文章內的範例。
person 這個 state 裡面有三個值分別為:firstNamelastNameemail,當我們在各自的 input 裡面想修改 state 裡的值,拿 firstName 當例子,就會寫成:

```JavaScript`
setPerson({
firstName: e.target.value,
lastName: person.lastName,
email: person.email
})


因為 `lastName` 跟 `email` 在我們修改 `firstName` 的時候希望照舊,所以就直接給他原本就在 `person` 裡的值。這時候可能只需要多寫兩個是還好,但當 object 需要 5 個甚至 10 個的時候,就會變得繁瑣。這時候就可以使用 [展開語法(spread syntax)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) 來複製之前的 object,剛剛的例子就可以寫成:
```JavaScript`
setPerson({
  ...person,
  firstName: e.target.value
})

這樣的意思就會是先複製 person 的所有內容到新的 object,然後再把新的 firstName 取代。注意如果順序反了的話:
```JavaScript`
setPerson({
firstName: e.target.value,
...person
})


就會是先給新的 `firstName`,再用舊的 `person` 內容取代,所以剛剛新的 `firstName` 就會再被覆蓋掉。

在使用展開語法的時候,我們也可以用變數放進 `[` 與 `]` 來當作想要更新的 object key,拿文章中的例子,會寫成:
``` JavaScript
function handleChange(e) {
  setPerson({
    ...person,
    [e.target.name]: e.target.value
  });
}

這樣更新就會根據 e.target.name 去更新相對應的 object key。e.target.name 通常是從 input 設的 name 去取得,像是 <input name="lastName" value={person.lastName} onClick={handleChange} />

更新巢狀 object

有時候 object 會更複雜,object 裡面會有另一個 object,使用文章中的範例:

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

person 底下還有 artwork 這個 object,而在 React,為了要讓 state 保持唯讀不可變的(immutable),就一樣需要使用展開語法:

setPerson({
  ...person, // 先複製 person
  artwork: { // 取代舊的 artwork
    ...person.artwork, // 複製 person.artwork
    city: 'New Delhi' // 取代舊的 person.artwork 裡的 city
  }
});

也可以使用區域變數先複製一個新的 object:

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

小結

今天關於 object state 的介紹先告一個段落,主要是會要知道如何使用展開語法,來讓我們的 state 保持 immutable 而不會受到 side effect 影響。官方文章後面還有介紹如何使用 immer 這個套件幫助我們更新 object state,我想留在明天再做詳細的介紹。
今天的文章先到這邊,感謝大家耐心地看完,如果有任何問題與建議歡迎告訴我,明天見,晚安。


上一篇
Day 18 - 將一系列的 State 更新加入隊列
下一篇
Day 20 - 更新 State 內的 Object Part 2
系列文
重溫 React 官方文件回到最初的起點20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言