今天繼續介紹 state 的運用,前面的文章的 state 更新都是用 數字 number
、字串 string
、布林值 boolean
等的單一型態(type)值,今天則是要來介紹可以儲存不同型態資料的 object
。
今天的文章參考官方文件的:
object
的話也可以參考:當使用 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
});
}}
再重新操作一次就可以發現紅球可以跟著我們的滑鼠一起移動了。
有些情況我們的 object
內容會有很多個值,但我們只需要更新其中一個,在寫 setter 的時候如果要把他全部填上去會很花時間並且讓程式碼很長,可以看文章內的範例。person
這個 state 裡面有三個值分別為:firstName
、lastName
與 email
,當我們在各自的 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,使用文章中的範例:
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,我想留在明天再做詳細的介紹。
今天的文章先到這邊,感謝大家耐心地看完,如果有任何問題與建議歡迎告訴我,明天見,晚安。