React Hook 分析 - useState
前言
什麼是「狀態 state」? 我認為可以用「驅動畫面的資料」做為理解-可以 記憶目前狀態,但可能會改變,改變狀態後,畫面也會隨之 改變
我們可以看看有哪些情況可以適用 state
- 頁碼 : 在畫面顯示 目前 是第幾頁,按下一頁之後,目前頁碼顯示也會隨之 改變
- 商品列表 : 在畫面顯示 目前 商品列表,但是點選其他分類後,商品列表也會隨之 改變
這就是為什麼 React 很多人喜歡以計數器做為範例,因為簡單又可以體現 state 的性質
- 顯示目前數字點擊到多少
- 當點擊 +1 畫面的數字也要跟著 +1
component 根據 state 的內容進行 render ,而當 state 被使用者行為改變時,component 也會隨著 state 一起改變
可以說 state 跟元件之間是綁在一起的生命共同體
我認為把 render 翻譯為 *渲染* 有一點不精確,因為 render 只是 React **完成最新畫面的施工藍圖**,而非瀏覽器真正渲染出那個畫面,往後我會參考 PJChen 大大,將 render 稱為 **轉譯**
useState 基本使用
import { useState } from "react";
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return <button onClick={handleClick}>Clicked {count} times</button>;
}
切記,hook 都要放在 function 的 top level,亦即不能被 for loop, if else 等包住,以免影響渲染運作
useState(0)
:- 以 0 作為初始值,並回傳一個陣列
- 裡面的兩個陣列物件,分別用
count
和setCount
來解構賦值
count
: state variable,保存元件渲染所需的狀態和資料
setCount
: state setter function,用來改變 state,而 state 被改變時,會 re-render 該元件
count
和 setCount
是綁定一起、天生一對,我們不能藉由去更改 count 的內容來觸發畫面重新渲染,而是透過 setCount(3)
來將 count
的值改為 3,並觸發重新渲染,顯示最新的 state
setState 的更新方式
方法一 : 直接指定
setCount(count + 1);
第一次 setState
- 取得 state 值
count
為 0 - 將值指定更新為 0+1,也就是
setCount(0+1)
第二次 setState
- 取得 state 值
count
為 1 - 將值指定更新為 1+1,也就是
setCount(1+1)
這樣就可以達到累加的效果
要注意的是,如果像以下方法撰寫,很可能會有問題
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<>
<h1>{number}</h1>
<button
onClick={() => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}}
>
+3
</button>
</>
);
}
這種寫法,按下按鈕並不會 +3,只有 +1 而已,為什麼呢? 假設 count
目前為 0,其實這種寫法等價於
setCount(0+1)
「請把 state 設為 1」
setCount(0+1)
「請把 state 設為 1」
setCount(0+1)
「請把 state 設為 1」
因為 state 在 render 期間並不會被改變,這樣只是重複 3 次一樣的動作罷了,接下來就要介紹 updater function
方法二 : update function
我們可以傳入一個函式到 setState 中
setCount((n) => n + 1);
這個方法是把 state 的更新放進去 queue 中,最後才依序執行
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<>
<h1>{number}</h1>
<button
onClick={() => {
setCount((n) => n + 1);
setCount((n) => n + 1);
setCount((n) => n + 1);
}}
>
+3
</button>
</>
);
}
和方法一不同的是,使用 updater function,按下按鈕就會直接 +3
queued update | n | returns |
---|---|---|
n => n + 1 | 0 | 0 + 1 = 1 |
n => n + 1 | 1 | 1 + 1 = 2 |
n => n + 1 | 2 | 2 + 1 = 3 |
更詳細的機制,可以搜尋 useState batch update
Render 性質
- 觸發 render
- 元件初始 render 會觸發,沒什麼懸念
- 元件 ( 或是他的父元件 ) state 被改變,就會觸發 render
- 開始 render 元件
- 開始編排所有 component 的施工藍圖
- 從 root component 進入,以遞迴方式往下探尋子元件
- React 的元件有 Functional Programming 的思想 - same input, same output
- 依據 DOM tree 的差異,開始變更 DOM 元素
- React 只會根據和上一次 render 的差異進行 DOM 操作,而不是無腦重新生成,這是為了效能考量
- 瀏覽器實際「畫出來」
State 與 Render 的關係
import { useState } from "react";
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button
onClick={() => {
setNumber(number + 5);
setTimeout(() => {
alert(number);
}, 3000);
}}
>
+5
</button>
</>
);
}
在這個範例可以發現,原本數字為 0 ,但按下按鈕後,畫面顯示最新的數字 5,但是過了 3 秒之後,彈跳視窗顯示的還是舊的 state 「0」,而非 5,為什麼會這樣呢?
我們可以想像每一次元件進行 render 時,都是按照當時的 state 的狀態拍下一張照片 ( snapshot ), state 的值在 render 過程中永遠不會變,以下分步講解
初始 state 為 0,按下按鈕:
- 將
setNumber(number + 5)
放在序列,render 完成後,將觸發 re-render ( 因為是 setState ) - 倒數計時,設定 3 秒後,會跳出 alert 數字 0 ( 因為現在的 state 還是 0 )
- 元件 render 完畢,觸發 re-render
re-render 完成,最後顯示 state 為 5,並且時間到,跳出 alert 數字 0
元件之間的 state 是獨立的
import Gallery from "./Gallery.js";
export default function Page() {
return (
<div className="Page">
<Gallery />
<Gallery />
</div>
);
}
<Gallery>
元件內部都有用到 state,但這兩個 <Gallery>
元件的 state 是獨立存在於 function 內部,不會互相影響