iT邦幫忙

2022 iThome 鐵人賽

DAY 2
1

標題下的冷笑話等級已經進入初老階段了嗎...

30 天的旅途前段會來回頭認識一些基本的 React Hook,而最常見到的就是 useState 本人。

Usage

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。

來看看如何使用吧!

與 Demo 一起伴讀

Example

下面是一個看到爛的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 )必定是更新後的數值。

Callback In setState (Updater)

Updater Function Ref

除了在 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 能夠確實連續加分啦!

Lazy Initial

有時候我們會提供的初始值需要先經過一些處理:

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)

Beware of Mutate

使用了 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)

https://ithelp.ithome.com.tw/upload/images/20220915/20151334LX0mVA4KGc.png

(第一次對 ... 的感覺如上XD)


感覺一來又對 useState 更熟悉了,下一篇再來看看 useEffect。

若本系列有錯誤的內容,歡迎隨時跟我告知。


上一篇
[DAY 01] 我知道 React,但 Hook 是什麼?
下一篇
[DAY 03] useEffect 與無止盡 loop 的距離
系列文
React Hook 不求人,建立自己的 Hook Libary30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
indexhui
iT邦新手 5 級 ‧ 2022-09-15 19:56:48

術士展開運算子

我要留言

立即登入留言