Day 16 第一次碰到 useState 時,我先把它理解成:
const [user, setUser] = useState(null)
React 幫我記住一份資料,而要改它時,用 setter。
但這裡一直有一個很直覺的問題。
假設只是做計數器:
const [count, setCount] = useState(0)
按下按鈕:
setCount(count + 1)
畫面就會從 0 變成 1。
可是 JavaScript 本來就會加一啊:
count++
那為什麼還要繞一圈寫:
setCount(count + 1)
今天就只追這一題:
普通變數明明可以改,為什麼 React 還需要 state?
先想像這個版本:
function Counter() {
let count = 0
function handleClick() {
count++
console.log(count)
}
return (
<button onClick={handleClick}>
點了 {count} 次
</button>
)
}
如果按三次按鈕,直覺上很容易猜:
點了 3 次
因為 count++ 明明有執行。
但這裡要先分開兩件事情:
JavaScript 裡的變數改變
≠
React 決定重新 render
在這個例子裡,點擊事件確實可以把目前那個 count 加一。
所以 Console 可能一路看到:
1
2
3
但單純修改普通區域變數,不會通知 React:
這份資料改了,請重新算一次畫面。
因此 JSX 裡原本 render 出來的:
點了 0 次
不會因為 count++ 自動更新。
[!NOTE]
這篇目前是草稿,repo 裡還沒有用實際 React 專案跑過這個例子。上面的觀察結果仍應在後續實作時用 Console 與畫面一起驗證。
count++ 到底做了什麼?先完全離開 React。
let count = 0
count++
console.log(count)
結果就是:
1
JavaScript 的工作沒有問題。
這行:
count++
做的是:
把這個 JavaScript 變數的值改掉
但 React 不會監聽程式裡每一個普通變數,只要它一變就重畫整個 Component。
如果真的那樣做,React 根本無法知道哪些變化只是暫存計算,哪些變化是真的會影響 UI。
所以問題不是:
count++有沒有成功?
而是:
React 有沒有被告知這個變化需要反映到畫面?
setCount 多做了什麼?換回 React 的版本:
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
function handleClick() {
setCount(count + 1)
}
return (
<button onClick={handleClick}>
點了 {count} 次
</button>
)
}
假設目前這次 render 裡:
count = 0
按下按鈕後:
setCount(count + 1)
不是直接把眼前這個 count 變數改成 1。
比較適合目前階段的理解是:
setCount(1)
↓
告訴 React:下一份 state 要更新成 1
↓
React 安排重新 render
↓
Component 再執行一次
↓
新的一次 render 取得新的 count
↓
JSX 再算一次
↓
畫面更新
所以 setter 的關鍵不只是「改值」。
它是透過 React 的 state 更新機制,讓 React 知道:
這份資料改了,而且這份資料屬於畫面狀態。
這又是另一個差異。
如果寫:
function Counter() {
let count = 0
只要 Component 因為任何原因重新 render,function 就會再執行一次。
也就是又走到:
let count = 0
普通區域變數不會自動幫我保存「上一次 render 的 UI 狀態」。
可以先把它想成:
第一次 render
Counter()
↓
let count = 0
下一次 render:
Counter()
↓
又建立新的區域變數
↓
let count = 0
但 state 不一樣。
const [count, setCount] = useState(0)
第一次 render 時,初始值是:
count = 0
之後如果 React 接到:
setCount(1)
下一次 render 時,它會把目前保存的 state 值提供回來。
所以 useState(0) 裡的 0 不是每次 render 都強迫把 count 洗回 0。
目前可以先讀成:
這份 state 第一次建立時,初始值是 0;之後由 React 幫我保存目前狀態。
以前看到:
const [count, setCount] = useState(0)
很容易覺得 React 只是把:
let count = 0
寫得更麻煩。
但如果把責任拆開,就比較清楚了。
普通變數:
拿來做 JavaScript 計算
↓
值可以在目前程式流程中改變
React state:
保存跨 render 需要記住的 UI 狀態
+
透過 setter 提出更新
+
讓 React 有機會重新 render
所以問題其實不是:
為什麼 React 不准我用
count++?
而是:
這個數字是不是一份會影響 UI,而且需要跨 render 保留下來的資料?
如果是,state 就有它存在的理由。
不是只有這行語法:
const [count, setCount] = useState(0)
而是:
普通變數改變
≠
React state 更新
以及:
setCount(...)
↓
提出 state 更新
↓
React 重新 render
↓
Component 再執行
↓
取得新的 state
↓
JSX 重新計算
↓
畫面更新
這樣再回頭看:
setCount(count + 1)
就比較不像一個硬背的 React 咒語。
它其實是在告訴 React:
這個值不是只有 JavaScript 自己要知道,下一次畫面也需要知道。