標題下的冷笑話等級已經進入初老階段了嗎...
30 天的旅途前段會來回頭認識一些基本的 React Hook,而最常見到的就是 useState 本人。
import { useState } from 'react'
const [state, setState] = useState(initValue)
若對
const [var, var] = ...
這樣的宣告語法不熟悉,這叫解構賦值(Destructuring assignment)
useState 就如其名,是一個控制狀態的 Hook,useState 就像 function 一樣,引入之後直接在 component 中使用 (而且是 Function Component。)
useState 可以接受一個初始值,並回傳出 state 與 setState function。
來看看如何使用吧!
下面是一個看到爛的counter計分功能的 component:
function Counter () {
//這邊使用了 useState,傳入 initValue 為 0,並將回傳內容命名為 score & setScore
const [score, setScore] = useState(0)
return (
<div>
<p>Player Score: {score}</p>
<button>Add Score</button>
<button>Sub Score</button>
</div>
)
}
我們將 score
安好了位置之後,畫面上就會看到 Player Score: 0,而 0 也就是我們一開始 useState
所設定好的初始值,接下來我們來使用 setScore
改變分數吧!
function Counter () {
const [score, setScore] = useState(0)
const handleAddScore = () => {
setScore(score + 1)
}
const handleSubScore = () => {
setScore(score - 1)
}
return (
<div>
<p>Player Score: {score}</p>
<button onClick={handleAddScore}>Add Score</button>
<button onClick={handleSubScore}>Sub Score</button>
</div>
)
}
inline 的寫法也是 OK 的
<button onClick={() => setScore(score + 1)}>Add Score</button>
handleAddScore
& handleSubScore
就如同一般的 event handler,分別對 score
進行加減的動作,而我們改變的 score
的方式是透過 setScore
(也就是 setState function) 進行改變。
const handleAddScore = () => {
//把要更新的值傳入setScore
setScore(score + 1)
}
透過 setScore
我們告訴 React score
要進行更新,React 會安排對畫面進行 re-render 的動作,以DEMO例子來看,會重新執行 Counter 這個 component function,React經過計算與判斷之後,畫面就會看到加分過後的 score
囉!
而後續的 re-render,useState
回傳的第一個值(也就是 score
)必定是更新後的數值。
除了在 setState()
中傳入要更新後的數值,我們也可以用 callback 來獲取最新的 state
。
const handleAddScore = () => {
// prev 必定會是最新的數值
setScore(prev => prev + 1)
}
這樣的寫法有甚麼好處呢?
假設我們要將 score
連續加兩次:
const [score, setScore] = useState(0)
//假設現在 score 為 0
const handleAddDoubleScore = () => {
// 我是錯誤的寫法
setScore(score + 1) // score 仍然是 0
setScore(score + 1) // score 仍然是 0
}
主要是因為 setState
是非同步的,因此上述的寫法實際執行下來是:
setScore(1) // score + 1 等同於 0 + 1
setScore(1) // score + 1 等同於 0 + 1
因此我們改用 callback (update function),透過 callback 傳進來的值會是最新的:
const handleAddDoubleScore = () => {
setScore(prevScore => prevScore + 1) //這時的prevScore為0 等同於 0 + 1
setScore(prevScore => prevScore + 1) //這時的prevScore為1 等同於 1 + 1
}
這樣我們就能確保 score
能夠確實連續加分啦!
有時候我們會提供的初始值需要先經過一些處理:
const [score, setScore] = useState(someMethod())
這樣做沒問題,但每次 re-render 時,儘管 useState 回傳的是最新的值,someMethod()
都會跟著被呼叫,若呼叫的 function 需要進行很大量的運算,那就真的是vERy eXpeNSivE!
因此,我們可以使用 callback 的方式安全的給予初始值
const [score, setScore] = useState(() => someMethod())
在 DEMO 中可以嘗試把這段程式碼(9-14 行)反註解並觀察 console 輸出的內容
// with lazy init
const [score, setScore] = React.useState(() => pretendToBeExpensive());
// with out lazy init,
// the function pretendToBeExpensive() will re-run every re-render
const [score, setScore] = React.useState(pretendToBeExpensive());
<StrictMode>
讓我們在開發中為了及早發現問題,render 會重複兩次,因此你會看到兩次的console.log 是正常的 (See Ref)
使用了 useState 之後,你可能想要對資料進行新增或刪除,對資料進行操作時,除了一定要透過 setState
,也要避免對 state
進行 mutate 。
若直接(或不小心)對 state
進行 mutate,資料與畫面可能會不如預期或顯示錯誤,進而難以掌握。
可以透過 clone 的方式來產生新的資料,來避免 mutate 。
const [list, setList] = useState([])
const handleAddData = () => {
// nono
list.push("Charlie")
//nono, too
list.push("Charlie")
setList(list)
// good
setList([...list, "Charlie"])
}
...
是 展開運算子(Spread syntax),雙胞胎弟弟是 其餘運算子(Rest syntax)
(第一次對 ... 的感覺如上XD)
感覺一來又對 useState 更熟悉了,下一篇再來看看 useEffect。
若本系列有錯誤的內容,歡迎隨時跟我告知。