前端框架
-React useState 2025 最詳細教學
this.web

useState 是什麼?
useState
是 React 中的一個 Hook,它的作用是讓「函數組件」擁有自己的狀態(state),當狀態改變時,React 會自動重新渲染畫面、更新畫面。同步狀態與畫面。
useState
的基本語法為:
const [state, setState] = useState(initialState)
useState
會返回一個包含兩個元素的陣列,這兩個元素分別為:
state
:當前的狀態值setState
:更新狀態的函數
通常我們會使用陣列解構賦值將這兩個元素命名為[something, setSomething]
(例如[count, setCount]
)。這可以讓程式碼更具可讀性而且符合 React 社群的最佳實踐。
我們可以傳入初始值 initialState
給 useState
,初始值可以是任何類型,像是數字、字串、布林值、物件、陣列甚至函式都行,如果我們沒有提供初始值,那初始狀態就會被設定為 undefined
。
接下來看看如何利用 useState 完成實務上常見的需求!
useState 詳細示範 - Toggle 開關
現在 PM 希望我們實作一個能夠顯示/隱藏內容的按鈕,當使用者按下按鈕時:
- 畫面上會出現或隱藏一段額外的文字內容,並且
- 按鈕文字會根據內容的顯示狀態切換,例如從「顯示內容」變成「關閉內容」
這個需求的關鍵點在於畫面狀態的切換。稍微分析一下需求後可以發現:
- 我們需要一個
boolean
值來記錄「內容目前是否顯示」 - 使用者每次按下按鈕時,要將這個布林值切換(
true ⇄ false
) - 畫面要根據這個布林值來決定要不要顯示內容
這正好符合 useState
的用途:管理函式組件中的狀態(state),並根據狀態更新畫面。
實作步驟
import { useState } from 'react';
function ToggleExample() {
// 利用 useState 建立狀態:控制內容是否顯示,初始值是 false
const [isOpen, setIsOpen] = useState(false);
// 切換狀態的函式
const toggle = () => {
setIsOpen(!isOpen);
};
return (
<div>
<button onClick={toggle}>
{isOpen ? '關閉內容' : '顯示內容'}
</button>
{isOpen && (
<p>這是一段額外的內容,只有當你點擊按鈕後才會顯示。</p>
)}
</div>
);
}
- 首先我們用
useState(false)
初始化isOpen
為false
,表示內容一開始是隱藏的。 - 接著用
setIsOpen(!isOpen)
切換狀態 - 在 JSX 的地方,我們就能利用
isOpen
來控制畫面要顯示什麼內容。{isOpen && (...)}
是條件渲染,當isOpen === true
時才會渲染<p>
標籤- 按鈕的文字透過三元運算子
isOpen ? '關閉內容' : '顯示內容'
來根據狀態切換。
這樣就很輕鬆的完成了 PM 的需求,所以如果當你的資料和畫面有關,而且當資料改變畫面也要跟著改變時,就可以使用 useState
來完成。
為什麼需要狀態 state?
這是蠻多新手都會有的疑問,也是很多人覺得 Vue 比 React 容易上手的一個原因,為什麼我們不能直接修改變數就好?需要用 state 呢?有 2 個原因:
一、隨意更改函數的輸入,會讓程式碼不容易維護
在程式世界中,如果隨意更改函數的輸入 (input),很容易讓程式碼變得不好維護,因為我們會不知道這個 input 變成甚麼樣子。舉例來說,如果我在 sum
函數中,隨意更改他的輸入,就會影響到函數外部的變數,像這個樣子:
function sum(num1, num2) {
num1 = num1 * 2; // 隨意更改 input 的值
return num1 + num2;
}
let x = 1, y = 2;
let z = sum(x ,y);
// 預期是 1 + 2 = 3
// 但因為更改了 input,變成 1 * 2 + 2 = 4
如果真的要更改輸入,比較好的做法是在函數裡新增一另一個變數來操作:
function sum(num1, num2) {
let num3 = num1 * 2;
// 執行要用到 num3 的操作
return num1 + num2;
}
相對的,在 React 中,我們也不能隨意更改組件的 Prop 值,所以如果我們要更新組件的 Prop,我們一樣需要在組件新增一個變數,也就是狀態 state 來控制組件。
二、React 需要知道什麼時候要重新渲染
React 是聲明式的框架,它必須知道什麼時候要更新 UI。使用 useState
可以讓 React 偵測到狀態變化並自動重新渲染畫面。
如果你只是用變數改值,React 不會感知到,畫面也不會更新。
useState 需要注意的 3 個細節
一、更新函式 Updater Function
在 useState
中,如果你傳入一個函數作為 setState
的參數,React 會將這個函數視為一個「Updater Function(更新函式)」。語法範例:
利用 Updater Function,我們可以拿到當前狀態的值,通常會使用 prev (也就是 previous) 或 state 名稱的第一個字作為參數名稱,例如
setCount(prev => prev + 1);
// 或是
setCount(c => c + 1);
筆者較喜歡第一種寫法,有時候會根據情況寫更仔細的 prevCount
這種寫法在同一個事件中多次更新狀態以及非同步操作後更新狀態的場景特別適合,比如我們寫一個按讚的功能,可以根據使用者是否使用加成來給更多的讚,如果我們這樣寫,會發現最後讚只增加 1 個
// 假設現在 likes = 0
const handleClick = () => {
setLikes(likes + 1); // setLikes(1 + 1)
if (isBoosted) {
setLikes(likes + 1); // setLikes(1 + 1)
}
if (isVIP) {
setLikes(likes + 1); // setLikes(1 + 1)
}
};
// 不管有沒有 boosted 或 VIP,likes 最後都只會是 2
這是因為 React 會先把 state 搜集起來,等到下次 render 時再一次更新,這種寫法 React 把最後一個運算是為最終結果,這種時候就要使用 Updater Function。
const handleClick = () => {
setLikes(prev => prev + 1); // setLikes(1 + 1) = 2
if (isBoosted) {
setLikes(prev => prev + 1); // setLikes(2 + 1) = 3
}
if (isVIP) {
setLikes(prev => prev + 1); // setLikes(3 + 1) = 4
}
};
二、物件與陣列的不可變性
更新陣列或物件的狀態時,我們不能直接修改原資料,而是要建立一份新的陣列或物件:
// 陣列狀態的更新
const [arr, setArr] = useState([1,2])
setArr(prev => [...prev, 3])
// 物件狀態的更新
const [user, setUser] = useState({ name: 'John', age: 20 });
setUser(prev => ({ ...prev, age: prev.age + 1 }));
這是因為 React 鼓勵 immutable (資料不可變性),簡單說就是狀態是 read-only (唯讀) 的,我們需要傳入新的陣列或函數才能讓 React 正確偵測到變化。
對於陣列,我們就要用展開運算符、map
、filter
、reduce
等會返回新陣列的方法,不能用 forEach
、pop
、push
、shift
等會影響原本陣列的方法。如果我們要對陣列狀態做增刪改,就要像這樣使用:
// 新增
setItems((prev) => [...prev, { id: 3, name: 'Cherry' }]);
// 刪除
setItems((prev) => prev.filter((item) => item.id !== 2));
// 修改
setItems((prev) =>
prev.map((item) => (item.id === 1 ? { ...item, name: 'Apple' } : item))
);
對於物件,我們需要特別小心有多層嵌套的物件,每一層都需要使用展開運算符返回新物件狀態(state):
const [user, setUser] = useState({
name: 'Alice',
address: {
city: 'Taipei',
zip: '100',
},
});
// 更新 User 時要展開兩層物件
setUser((prev) => ({
...prev, // 👈 展開第一層
address: {
...prev.address, // 👈 展開第二層
city: 'Tainan',
},
}));
三、狀態更新是非同步的
在 React 中,當你呼叫 setState
(如 setCount(1)
)時,React 並不會立即更新該狀態,也不會馬上重新渲染組件。取而代之的做法是:
- React 會先把這個狀態更新請求放進一個「佇列(queue)」中
- 等 React 要進行重新渲染前才會統一執行這些狀態更新並觸發 re-render
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(1);
console.log(count); // ❌ 這裡仍然是 0,不是 1
};
這種設計目的是為了效能優化,避免每次更新就立即重渲染,導致性能下降。
更詳細的機制可以參考:Batch Update 是什麼?
useState 的 3 個進階用法
一、惰性初始化 (Lazy Initialization)
這名詞聽起來很難,但其實很單純,如果你的初始值是需要計算出來的,可以傳一個 function 給 useState,比如某個組件要根據傳入的時間算出剩餘時間,就可以先傳入 function 來計算
function Countdown ({targetTime}) {
const [leftTime, setLeftTime] = useState(() => calculateLeftTime(targetTiem))
return (...)
}
這個函數只有在第一次渲染時才會執行。但要注意這邊不能這樣寫,這會導致每次 re-render 都觸發函數,造成多餘的運算
function Countdown ({targetTime}) {
const [leftTime, setLeftTime] = useState(calculateLeftTime(targetTiem))
// ...
}
二、派生狀態 Derived State
若某些狀態可以從其他資料計算得出時,就不一定要使用 useState
,可以直接用變數處理,避免不必要的狀態儲存:
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + lastName;
return ...
}
三、使用 Key 重置 state
React 中可以利用 key
強制組件重新掛載,比如我要根據 userId 重置 ProfilePage
裡面的 state,就可以利用 key 值做到
export default function ProfilePage({ userId }) {
return (
<Profile
userId={userId}
key={userId}
/>
);
}
function Profile({ userId }) {
const [comment, setComment] = useState('');
// ...
}
每次 key
改變,Profile
就會被重建,state 也會重設。
useState 搭配 TypeScript 的用法
useState 也能利用泛型(Generics) 做各種組合,最基本的就是直接指定型別,像是這樣:
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<{ name: string, age: number }>({ name: '', age: 0 });
而如果初始值是 null
、undefined
,或你需要等待資料載入時,我們也要明確告知可能的型別組合:
const [data, setData] = useState<DataType | null>(null);
我們也能使用物件和陣列組合的型別,例如 TodoList 通常會有多個 Todo,而每個 Todo 又會有各種資料在裡面,我們就能這樣寫:
type Todo = {
id: number;
text: string;
done: boolean;
};
const [todos, setTodos] = useState<Todo[]>([]);
還能搭配 Enum 或聯合型別,尤其 Enum 特別好用,可以讓我們快速知道有哪些狀態:
type Theme = 'light' | 'dark';
const [theme, setTheme] = useState<Theme>('light');
// Enum
enum Status {
Idle = 'idle',
Loading = 'loading',
Error = 'error',
Success = 'success',
}
const [status, setStatus] = useState<Status>(Status.Idle);
當然,我們也能讓 TypeScript 自己去推斷型別,適合單純簡單的狀態:
const [isOpen, setIsOpen] = useState(false); // 推論為 boolean
最後介紹一個稍微進階一點的用法,利用泛型(Generic) 和 custom hook 可以做出更通用的 state,假如我們的程式裡面有各種資料結構類似的陣列,我們就能封裝一個 useList 簡略 type 的寫法:
function useList<T>(initial: T[]) {
const [list, setList] = useState<T[]>(initial);
return [list, setList];
}
const [list, setList] = useList<number>([1, 2, 3]);
const [todo, setTodo] = useList<Todo>([]);
常見的 useState 疑問
狀態 state 已更新,但 console.log 仍顯示舊值?
因為狀態更新是非同步的。你不能馬上在更新後讀取最新值。就像上面 “狀態更新是非同步的” 的小結示範一樣,要解決這個問題,我們可以使用 useEffect
來觀察值變化。
const [count, setCount] = useState(0);
useEffect(() => {
if (count === 3) {
console.log('count is 3');
}
}, [count])
const handleClick = () => {
setCount(count + 1);
};
Too many re-renders
通常是因為你在組件執行階段(render phase)直接呼叫了 setState
,造成無限迴圈。
// 錯誤寫法
const [count, setCount] = useState(0);
setCount(count + 1); // 這會不斷重新渲染
因為調用 setState 就會讓 React 知道要重新渲染,如果你直接在組件裡這樣使用 setState,React 就會瘋狂 Re-render。
狀態 state 已更新,但畫面未更新?
如果你沒有正確呼叫 setState
或直接修改了 state 的內容(像是直接改陣列、物件),這樣 React 不會觸發重新渲染。
const addItem = () => {
items.push(newItem); // 錯誤寫法
};
就像上面陣列和物件的更新方式一樣,要用返回新的陣列或物件的更新方式:
setItems(prev => [...prev, newItem]);
狀態 state 不能在判斷語句中使用
在 React 中,useState
只能在函數組件的頂層調用,而不能在循環、條件語句或嵌套函數中使用。
這是因為 React 依賴 Hook 的調用順序來管理狀態。每次渲染時,React 都會按照固定順序執行所有的 Hook,如果 Hook 的順序發生改變,React 將無法正確追蹤狀態,可能導致狀態錯亂或錯誤。
錯誤示範:
function ExampleComponent({ condition }) {
if (condition) {
const [count, setCount] = useState(0); // 錯誤:useState 不應該在條件語句中
}
return <div>Example</div>;
}
在這個例子中,useState
只會在 condition
為 true
時執行。這樣就會導致 Hook 的調用順序在每次渲染時變化,React 將無法正確匹配狀態值,導致應用程序行為不一致。
正確示範:
正確的做法是確保所有 Hook 都在組件頂層調用,無論條件是否成立,這樣可以保證每次渲染時 Hook 的調用順序保持一致。
function ExampleComponent({ condition }) {
// 確保 useState 在組件頂層調用
const [count, setCount] = useState(0);
if (condition) {
return <p>Condition is true, count: {count}</p>;
} else {
return <p>Condition is false</p>;
}
}
總結
useState
是讓函數組件有狀態的 Hook,當狀態變化會觸發 React 重新渲染畫面,使用時要注意狀態不可變的原則,要返回新陣列或物件,以及非同步性特性。
文章還介紹了如何搭配 TypeScript 做使用,熟悉 useState
是成為 React 開發者的重要第一步。熟悉 useState,才能撰寫更複雜、可維護的互動式 UI。