React 核心觀念解密:setCount 究竟是什麼?為何它能同時接收數值與函式?
更新日期: 2025 年 4 月 15 日
在學習 React 的過程中,很多新手會遇到這個看似簡單卻超關鍵的問題:
setCount(count + 1)
和 setCount(prev => prev + 1)
差在哪裡?
什麼時候該用哪一種?為什麼可以這樣寫?
這篇文章將從最根本的語法開始解釋,再深入到 React 背後的運作邏輯,並透過實際情境幫你徹底搞懂兩者的差異與使用時機。
setCount
是什麼?為什麼能這樣用?
useState
回傳的是一組陣列
當你這樣寫:
const [count, setCount] = useState(0);
這行程式其實做了兩件事:
useState(0)
:呼叫後會回傳一個陣列,內容為[目前的狀態值, 更新這個狀態的函式]
也就是像這樣的結果:const result = [0, function];
[count, setCount] = ...
:這是一種 JavaScript 語法,叫做「解構賦值(destructuring assignment)」,用來從陣列中快速取出元素並指定變數名稱。
所以這段語法的意思是:
- 把
useState(0)
回傳的陣列第一個值(狀態)取出來命名為count
- 把第二個值(更新函式)取出來命名為
setCount
簡單圖解
const result = useState(0); // result = [0, setCountFn]
const count = result[0];
const setCount = result[1];
使用解構賦值後,這三行可以簡化成一行:
const [count, setCount] = useState(0);
setCount
的兩種寫法
React 提供了兩種方式來更新狀態值 count
,你可以根據實際情況選擇使用:
傳入「值」:直接指定新的狀態
setCount(5);
意思是:我要把 count
改成 5。這是最直覺的方式,React 會立即把 count
更新為指定的數值,並觸發畫面重新渲染。
傳入「函式」:根據舊值計算新值
setCount(prev => prev + 1);
這種寫法的好處是,你不需要依賴外部變數,而是直接根據目前的狀態值 prev
來計算新值。React 在執行時會幫你把當下的 count
傳進去,並以此結果作為新的狀態。
差異總整理
類型 | 寫法 | 適用情境 |
---|---|---|
傳入值 | setCount(5) | 已經知道最終狀態值 |
傳入函式 | setCount(prev => prev + 1) | 需要根據舊值動態計算 |
那 count + 1
是函式嗎?不是,它只是「值」
看到這裡你可能會想問:
「那我這樣寫
setCount(count + 1)
是不是也算傳入函式?」
其實不是。這邊的 count + 1
是一個表達式,會先被計算成一個數值,然後這個值才會被傳進 setCount
。
例如:
const count = 3;
setCount(count + 1); // 等同於 setCount(4)
這完全屬於「傳入值」的寫法,而非函式寫法。
為什麼 React 支援兩種寫法?
React 設計 setState
或 useState
時,特別保留了「可以傳值」或「傳函式」兩種用法,是為了因應不同的使用情境,讓開發者寫起來既靈活又安全。
情境一:我知道我要的值是什麼 → 傳入「值」
這是最常見的情況,例如你想把 count
設為 0,就直接寫:
setCount(0);
在這種情境下,你完全不需要根據舊值來做任何計算,所以直接給一個「明確的新值」最簡潔直觀。
適用的例子有:
- 表單送出後,重設為初始值
- 點擊「歸零」按鈕
- 根據條件直接給定數值
情境二:我需要根據「目前狀態」來推算新值 → 傳入「函式」
這種寫法會把目前的狀態值傳進你提供的函式中,讓你可以依據它來計算新的狀態。
setCount(prev => prev + 1);
這就像是對 React 說:「請你把最新的 count 傳進來,我來算出新值」。
這非常適合處理以下情況:
- 多次連續更新(例如點三次加三)
- setTimeout、setInterval 等非同步更新
- 根據目前狀態進行邏輯判斷或加減乘除
小結
React 支援兩種方式是為了讓你在以下兩種場景都能寫出簡潔且正確的程式碼:
你面臨的情況 | 最適用的寫法 |
---|---|
我知道新值是什麼 | setCount(數值) |
我需要根據舊值來推算新值 | setCount(prev => 新值運算) |
背後的 JavaScript 原理:參數可以是任意型別
這樣的設計在 React 中看起來好像很「特別」,但其實完全符合 JavaScript 的基本語言特性。
JavaScript 是一種「動態型別語言」,它的函式可以接受任何型別作為參數,不需要事先宣告。
來看一個簡單的範例:
function example(param) {
console.log(param);
}
你可以這樣呼叫它:
example(1); // 傳入數字
example("hi"); // 傳入字串
example(() => {}); // 傳入函式
example([1, 2, 3]); // 傳入陣列
example({ key: "value" }) // 傳入物件
這代表:函式接受函式作為參數這件事,在 JavaScript 中是完全合法、常見、而且非常強大的語言特性。
應用在 React 的場景
setCount()
就是一個普通的函式,它可以接受數值,也可以接受函式:
- 傳入數值:React 會直接設為新狀態
setCount(5); // 直接設為 5
- 傳入函式:React 會在內部呼叫這個函式,把目前的狀態傳進去,然後再根據回傳值更新狀態
setCount(prev => prev + 1); // React 傳進 prev,執行後回傳新值
這就是 JavaScript 本身的彈性所賦予 React 的功能。
延伸設計概念:型別判斷 + 多型邏輯
React 內部其實是這樣判斷的(簡化版本):
function setCount(action) {
const newValue = typeof action === 'function' ? action(currentValue) : action;
// 接著用 newValue 來更新狀態
}
這種設計模式稱為:
函式多型(Function Overloading by Input Type)
同一個函式根據「參數型別」決定不同的處理邏輯
這在設計 API 或大型函式庫時非常常見,能同時兼顧使用者體驗與語意清晰。
實際情境範例分析:何時該用哪種方式?
情境 1:表單重設或按鈕歸零
✅ 使用 setCount(5)
function ResetCounter() {
const [count, setCount] = useState(10);
return (
<div>
<p>目前計數:{count}</p>
<button onClick={() => setCount(0)}>重設為 0</button>
</div>
);
}
✅ 解釋:
你很確定自己要的值是「0」,這時候用 setCount(0)
是最直接、最清楚的寫法
不需要根據之前的 count 去算,只是直接指定值
情境 2:按一下按鈕就 +1
✅ 使用 setCount(count + 1)
或 setCount(prev => prev + 1)
都可以
function ClickCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>你點了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
✅ 解釋:
這裡每次點按鈕只會執行一次,count
是最新的值,所以你可以用 setCount(count + 1)
,寫法簡單、可讀性好。
🟡 不過其實
setCount(prev => prev + 1)
也行,只是比較進階安全。
情境 3:在同一個事件中多次更新狀態
❗ 必須用 setCount(prev => prev + 1)
function AddThree() {
const [count, setCount] = useState(0);
const handleAddThree = () => {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<p>目前:{count}</p>
<button onClick={handleAddThree}>+3</button>
</div>
);
}
❌ 錯誤寫法(會失效):
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
三次都是拿到一樣的舊值,結果只會加 1 😱
情境 4:延遲更新(例如倒數計時)
✅ 用 setCount(prev => prev - 1)
更安全
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev - 1);
}, 1000);
return () => clearInterval(timer);
}, []);
✅ 解釋:
你要每秒減 1,但 setInterval 執行時,外層的 count
可能早就過期了
所以用函式的方式 setCount(prev => prev - 1)
可以確保每次都是從「最新的值」做計算 ✅
情境 5:依照目前 count 判斷下一步要做什麼
✅ 可以用條件 + 值型態更新
const handleNext = () => {
if (count >= 10) {
setCount(0); // 達到最大值就重設
} else {
setCount(count + 1); // 否則繼續加
}
};
✅ 解釋:
你不是一直連續呼叫 setCount,而是一次判斷 → 一次更新,這種情況 count + 1
是沒問題的。
總結對照表
情境 | 使用方法 | 原因說明 |
---|---|---|
要設定成明確的數字 | setCount(0) | 不依賴前一個值,直接指定 |
單次 +1(按鈕點一下) | setCount(count + 1) | 同一 render、可讀性高 |
多次連續更新(加三、加十) | setCount(prev => prev + 1) | 每次都從最新值運算,避免只加一次 |
非同步更新(如 setTimeout) | setCount(prev => prev - 1) | 保證拿到最新的值做運算 |
需要條件判斷後決定是否更新 | setCount(...) 根據邏輯選擇 | 依情況決定是傳值還是用函式 |
進階觀念:什麼是「函式式更新」(Functional Updater)?
React 的狀態更新是「非同步且可能被合併的」。
因此:
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
React 會把三次 count
都視為「舊值」,結果只更新一次!
改用:
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
這時 React 會依序更新:
- 第一次:0 → 1
- 第二次:1 → 2
- 第三次:2 → 3
生活比喻幫助理解:React 郵局號碼牌
想像你去「React 郵局」領號碼牌(state),你現在的號碼是 count = 0
。
你對窗口說:
「幫我號碼 +1」
React 會回你說:
「好,我等一下幫你處理喔,請去後面排隊」
✅ 情境 A:你只講一次 setCount(count + 1)
setCount(count + 1);
你說了一次「號碼 +1」,React 處理時會看你當時說的是多少:
count = 0 → 0 + 1 → 幫你更新成 1 ✅
沒問題。
❌ 情境 B:你在同一秒講了三次
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
你連講三次「請幫我 count +1」
但 React 是怎麼聽的呢?
每一次你說的 count
其實都是 當下你「看到的數字」,也就是 0
。
React 會幫你排隊處理,但都是:
0 + 1 → 更新成 1
0 + 1 → 更新成 1
0 + 1 → 更新成 1
所以最後 count
還是只有變成 1
!
因為你每次都看舊的號碼牌在講話 🪪
✅ 情境 C:用 prev => prev + 1
的寫法
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
你現在說的是:
「React,我不看號碼,我要你幫我:根據你最新看到的號碼,加 1」
React 在處理第一個時:
prev = 0 → +1 → 1 ✅
第二個:
prev = 1 → +1 → 2 ✅
第三個:
prev = 2 → +1 → 3 ✅
這時候你就拿到真正更新三次的結果,count = 3
🎉
結語:這不只是語法,是理解 React 的核心
學會分辨「傳入值」與「傳入函式」的用法,是掌握 React 狀態管理邏輯的第一步。
當你理解這個機制後,未來使用 useState
、useReducer
或第三方狀態管理工具時,都會更得心應手。
如果你想要延伸練習這種「依據參數型別切換行為」的設計邏輯,也可以自己試著寫一個 mySetState()
,讓你更理解 JavaScript 的彈性!