React 元件可以有自己的資料?state 基礎觀念與使用
更新日期: 2025 年 4 月 14 日
《React 元件基礎介紹》:
- React 的開發邏輯:為什麼現代前端要用元件?
- React 元件是什麼?畫面積木的基本單位
- 什麼是元件樹?理解元件的父子與巢狀結構
- 撰寫第一個 React 元件:函式型元件入門
- 如何規劃與拆分元件?從 UI 切割建立模組思維
- 元件之間如何傳資料?Props 教學入門
- 初學者也能懂!React children 全解析與實戰教學
- React 元件可以有自己的資料?state 基礎觀念與使用
- 什麼是 prop drilling?當資料傳遞變成麻煩
- Context API 是什麼?解決 prop drilling 的痛點
- 比 Context 更好用的選擇?認識第三方狀態管理工具
在閱讀本文前,建議先閱讀《JSX 基礎介紹》
你在學習 React 的過程中,可能已經習慣用 props
傳資料給元件,但你可能也會想問:
「有些資料是元件內部自己要記住的,不能靠父母提供,怎麼辦?」
例如:點擊按鈕要切換畫面狀態、輸入欄位要記住使用者輸入的文字、計時器要記住秒數⋯⋯這些資料,是元件自己產生、自己使用的,不適合從外面傳進來。
這時候,React 就提供了 state(狀態) 這個超好用的機制,讓你的元件像有「小腦袋」一樣能記住資料!
什麼是 state?
定義:元件內部的「變動資料儲存空間」
在 React 中,每一個元件都是一個可以根據資料狀態改變畫面的獨立單位。
而這些「資料狀態」就被稱為 state
。
簡單來說:
state
是一種用來記住元件內部資料的機制,它的值可以被更新,而且一更新就會自動觸發畫面重新渲染(re-render)。
這讓使用者在和畫面互動時(例如:按按鈕、輸入文字、點擊切換)可以即時看到最新的反應,而不需要你手動操作 DOM 來改變畫面,這正是 React 的強大之處。
把 state 想成元件的「小腦袋」
每個元件好像都擁有自己的「小腦袋」,它可以自己記住某些資料,不需要靠別人幫它記。
例如:
- 表單中的輸入內容
- 計數器目前的數字
- 一個列表目前有幾個項目
- 一個 modal 是否正在開啟
- 使用者目前點選了哪個頁籤
這些都是元件自己要記住的資料,它們會隨著使用者的操作而改變,也就是我們說的「變動資料」。
這種場景就適合使用 state
來處理。
當你更新 state 時,會發生什麼事?
當你使用 React 提供的 setState
函式(或 Hook 的 setX
),改變了某個 state
值時:
- React 會自動偵測到這個元件的資料變了
- 它會觸發這個元件重新渲染
- 畫面就會即時更新,反映最新的資料
這就是為什麼我們說 React 是「聲明式」框架:你只要負責設定資料,React 幫你更新畫面。
state 跟 props 差在哪?
state
常常和 props
被放在一起講,因為它們都是「資料來源」,但其實概念完全不同:
角色 | 資料來源 | 誰可以修改 | 適合用途 |
---|---|---|---|
props | 外部元件傳進來 | 不能修改 | 接收父元件提供的資料,像是參數 |
state | 自己元件內部 | 可以修改 | 元件自己記錄的資料,例如輸入值、UI 狀態等 |
📖 更生活化的比喻
角色 | 說明 |
---|---|
props | 像是父母交代的小任務,不能隨便改,乖乖照做 |
state | 像是自己的筆記本,想記什麼就記什麼 |
所以當你遇到一筆資料:
- 是「別人給你的」 → 用
props
- 是「自己產生的、自己要改的」 → 用
state
判斷是否使用 state 的小技巧
問自己這三個問題:
- 這筆資料是由元件自己管理的嗎?
- 這筆資料會不會因為互動而改變?
- 資料變動時,畫面需要跟著更新嗎?
如果答案是「是」,那這筆資料就適合放在 state
中管理。
✅ 總結一句話:
props
是「從外部來的、你不能碰的資料」,state
是「你自己生的、你可以更新的資料」。
useState 基本用法
在學 React 的時候,我們會接觸一種叫「函式型元件」的寫法,它看起來就像一個普通的 JavaScript 函式,負責畫出一塊畫面。
但問題來了 ——
React 的函式型元件就像是「印表機」——每次都根據資料印出一張畫面給你看,但印完之後,它不會記得你剛剛按了什麼、也不會主動改內容。
如果你想讓它「記得你按了幾次」、「根據互動去改變畫面」,就需要額外的功能來幫忙 —— 這時候 Hook 就登場了!
🧩 想像一個沒有 Hook 的畫面會怎樣?
你做了一個按鈕,畫面上寫著「點我 +1」。但每次點完按鈕,畫面都沒變,數字也不會更新……因為你沒有記住點了幾次。
🎣 這時候就要靠 Hook 出場!
React 幫你準備了一些小工具叫做 Hook(鉤子),你只要把 Hook 掛上去,元件就會變得更厲害!
其中最基本的 Hook 就是 👉 useState()
useState()
是什麼?
你可以這樣理解:
useState()
就像是幫你的元件裝上一個「記憶體格子」,可以放資料進去,然後在畫面上用出來。當你把資料改掉的時候,React 會自動幫你「重畫」畫面,顯示最新的內容!
📦 實際例子:加一按鈕
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>目前的數字是:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
🧾 看不懂沒關係,我幫你翻譯成白話:
// React,請幫我準備一個「記憶格子」來存數字,初始是 0
const [count, setCount] = useState(0);
// 之後我會用 setCount 來改這個數字
// 每次我按一下按鈕,數字 +1,React 自己會幫我把畫面更新
為什麼這麼神奇?
因為這不是普通的變數,它是 React 的「狀態系統(state)」。
你只要透過 useState()
宣告,並用 setX()
改它,React 會自動幫你完成這些事:
- 更新資料
- 重新執行元件(重新畫畫面)
- 顯示最新的值
不用自己操控畫面、不用直接動 HTML,就能做出互動效果!
✅ 一句話讓你記住
useState()
就是讓你的元件「會記東西、改東西、還能自己更新畫面」的魔法工具。沒它的話,元件就只能呆呆地顯示畫面、不能互動。
程式碼逐行拆解說明
import { useState } from 'react';
✅ 這行在做什麼?
這是把 React 提供的 Hook —— useState
函式引入到你的元件中。
你只要看到 useState
,就要知道:「這是用來讓元件記住資料」的工具。
💬 簡單來說:這行是在跟 React 說:「我這個元件會用到記憶資料的功能,先借我用一下
useState
!」
const [count, setCount] = useState(0);
✅ 這一行超重要,是整個 useState
的核心語法。
你可以把它翻譯成:
「我想要有一個變數叫做
count
,一開始的值是0
。之後如果要改這個值,請用setCount()
這個函式幫我改。」
這一行幫你做了兩件事:
成員 | 用途說明 |
---|---|
count | 用來存你現在要顯示的數字(state 本身) |
setCount | 是用來改 count 的唯一方式,React 才能知道要重畫畫面 |
🧠 React 會記得這個
count
的值存在它自己的系統裡。你不需要自己管變數,也不用放在全域變數中,它會幫你管理得好好的。
❓ setCount 是從哪來的?我沒定義它啊?
你會發現,雖然我們沒自己寫 function setCount() {...}
,但它卻能直接使用,這到底是怎麼回事?
其實,這一切的魔法都來自這行程式碼:
const [count, setCount] = useState(0);
這行用了 JavaScript 的 陣列解構賦值(destructuring assignment) 技巧。
📦 useState(0)
回傳的是什麼?
useState(0)
會回傳一個陣列,裡面有兩個東西:
const stateArray = useState(0);
// 內容像這樣: [目前的值, 改變這個值的函式]
所以這行:
const [count, setCount] = useState(0);
就等同於:
const count = stateArray[0]; // 目前的值
const setCount = stateArray[1]; // 專門用來更新這個值的函式
✅ 所以,setCount
是從哪裡來的?
它不是你自己定義的,也不是 React 寫死的函式,而是你從
useState()
回傳的陣列裡「自己取名字接住」的更新函式。
這個更新函式是 React 幫你根據目前這筆 state 動態產生的,一旦你呼叫它,React 會幫你做以下幾件事:
- 把新值存起來
- 觸發這個元件重新執行(re-render)
- 更新畫面,顯示最新狀態
💬 白話比喻一下:
你可以把 useState()
想成一個「小工具包」,每次你呼叫它,它就幫你生一組資料組合:
function useState(initValue) {
let currentValue = initValue;
function setValue(newValue) {
currentValue = newValue;
renderAgain(); // React 幫你重新 render 畫面
}
return [currentValue, setValue];
}
雖然實際實作比這複雜很多,但概念上你可以這樣理解。
🔁 結論一句話:
setCount
是useState()
回傳的「更新用函式」,你取什麼名字都可以,但建議用setX
命名,方便閱讀與維護。
所以你會看到我們常這樣寫:
const [name, setName] = useState('');
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<p>目前的數字是:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
✅ 這部分是畫面(JSX),說明如下:
JSX 區塊 | 白話說明 |
---|---|
<p>目前的數字是:{count}</p> | 把 count 的值顯示在畫面上。React 每次 render 會自動帶入最新的值。 |
<button onClick={...}>+1</button> | 當使用者按下按鈕,會執行 setCount(count + 1) → 數字就會加 1 |
🔁 setCount 發生了什麼事?
每次你呼叫 setCount()
,React 幫你做了以下幾件事(超重要!整個機制的靈魂!):
1️⃣ 把新的值存在 React 內部的記憶區
setCount(新的值)
→ React 記下你給的「最新狀態」
2️⃣ 重新執行這個元件函式(例如 Counter())
React 會自動重新執行整個元件,把最新的 count
套到畫面裡的 {count}
中。
注意:React 不是只更新那一小塊畫面,而是整個 return()
再跑一次(但它會優化處理,只改變必要的地方)
3️⃣ 更新畫面(render)給使用者
你什麼都不用做,React 自動幫你:
- 更新 HTML
- 顯示新的值
- 重新排版畫面
這就是為什麼說:「React 幫你管畫面更新,你只要管資料的變化」。
🧙♂️ 不用手動操作 DOM 的魔法
在過去(像是 jQuery 時代),如果你要點按鈕加數字,還得:
- 自己寫變數
- 自己抓 DOM
- 自己改
innerText
而現在你只要:
setCount(count + 1);
就好了,剩下的 React 幫你全包!
命名建議:變數 + set 函式配對命名,為什麼重要?
在使用 useState()
時,我們通常會寫這樣的語法:
const [變數, set變數] = useState(初始值)
這種寫法雖然只是語法上的「命名習慣」,但它背後的邏輯其實很實用、很有意義!
✅ 為什麼推薦這種命名方式?
好處 | 說明 |
---|---|
🧭 清楚表達用途 | 一看就知道哪個是資料,哪個是用來更新它的函式 |
🤝 提升團隊可讀性與協作性 | 讓你和其他開發者讀程式碼時更直觀,不用猜那個 set 是控制哪個變數 |
🧠 記憶容易,不易混淆 | 多個 state 同時存在時,不會搞混誰對應誰,維持邏輯一致性 |
✨ 常見範例(配對命名範本):
const [name, setName] = useState(''); // 字串 → 表單輸入
const [isVisible, setIsVisible] = useState(false); // 布林值 → 控制顯示/隱藏
const [items, setItems] = useState([]); // 陣列 → 管理列表
const [user, setUser] = useState(null); // 物件 → 儲存 API 回傳的使用者資料
你會發現,這些命名方式都很直覺 ——state 是什麼資料,setXXX 就是對應的操作方法。
🧠 小提醒:不是一定要這樣寫,但建議這樣寫!
React 並不強制你要用 setX
這種命名方式。
你完全可以這樣寫:
const [data, changeData] = useState(123);
語法上完全正確,但這樣會讓人讀到 changeData()
的時候,還要回去找它對應哪個變數。
如果你照慣例命名成:
const [data, setData] = useState(123);
大家一看就懂,省去很多「看懂程式碼」的時間。
✅ 命名風格不只讓你自己開發順手,更是團隊協作、維護大型專案的基石。
useState()
的初始值可以是什麼?
useState()
接受的參數就是「你要這個變數一開始是什麼值」,而這個值可以是:
資料類型 | 範例 | 常見應用情境 |
---|---|---|
數字 | useState(0) | 計數器、分頁、等級等 |
字串 | useState('') | 表單欄位、搜尋框 |
布林值 | useState(false) | 顯示/隱藏開關、勾選狀態 |
陣列 | useState([]) | 商品列表、留言列表等 |
物件 | useState({ name: '', age: 0 }) | 儲存複合資料、表單整包狀態 |
💡 初始值的類型會影響後續你怎麼設計元件的顯示邏輯與操作邏輯,設對型別很重要!
⚠️ 小提醒:state 是不可變的(immutable)
這句話的意思是:
你不能直接修改 state 裡面的內容,要用
setX()
去「取代」整個值。
🚫 錯誤範例(這樣畫面不會更新):
state.value = 123; // ❌ 錯誤!這不會觸發重新 render
✅ 正確做法:
setState({ ...state, value: 123 }); // ✅ 替換整個物件,才會更新畫面
這也是為什麼我們常常會用「展開運算子」(...
) 去維持其他資料不變,只改某一個欄位。
state 的互動範例:文字輸入框
我們前面已經看過 useState()
的基本用法(像是加一的計數器),但在實際開發中,最常見、最實用的情境之一,就是:
✅ 讓使用者輸入資料,並把這些資料「即時記下來、同步顯示」在畫面上。
這時候就會用到 state + input 輸入欄位 的組合。
✅ 範例程式碼
function InputExample() {
const [text, setText] = useState('');
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>你輸入的是:{text}</p>
</div>
);
}
背後邏輯一步步拆解
這個元件看起來很簡單,但其實裡面包含了 React 非常重要的概念!來逐行解析 👇
const [text, setText] = useState('')
這一行代表:
- 我們宣告了一個 state:
text
- 初始值是空字串(表示輸入框一開始是空的)
setText()
是更新這個text
的方法
<input value={text} onChange={(e) => setText(e.target.value)} />
這行非常關鍵,我們做了兩件事:
屬性 | 說明 |
---|---|
value={text} | 把 state 裡的值指定為 <input> 的值 → 讓畫面「跟 state 綁定」 |
onChange={...} | 當使用者輸入文字時,把文字更新到 text(透過 setText) |
也就是說,這個輸入框不是自己決定要顯示什麼,而是根據 state 的值來顯示!
<p>你輸入的是:{text}</p>
這行就是把 text
顯示出來,當使用者每打一個字,state 被更新,React 就會自動重新 render 畫面,這行也會跟著更新。
React 是怎麼運作的?流程如下:
- 使用者在 input 輸入文字
- 觸發
onChange
事件,執行setText(e.target.value)
text
的值被更新(state 改變)- React 偵測到 state 改變 → 自動重新執行這個元件
- 畫面重新 render,
<input>
和<p>
都根據最新的text
顯示內容
這個流程非常流暢,你不用手動操作 DOM、也不用自己控制輸入內容,全部都交給 React 的 state 管理機制。
🔁 這種機制有個名字:雙向綁定(Two-way Binding)
什麼叫雙向綁定?
資料的變化可以影響畫面,畫面的互動也會改變資料
在這個例子中:
- 畫面(input) → 改變資料(透過 onChange)
- 資料(state) → 改變畫面(透過 value)
雙向互通,資料與畫面保持同步,這就是表單處理的核心。
初學者常見疑問:為什麼不能只寫 value=""
?
有些人會這樣寫:
<input value="" />
或
<input value={text} />
但不加 onChange
,結果就會看到一個「無法輸入的輸入框」!
因為:
React 將你設定的
value
視為「這就是 input 的唯一來源」,如果你沒告訴它怎麼改,它就不會讓你輸入!
所以:只要加上 value
,你就必須搭配 onChange
來讓它能改變 → 這就叫做「受控元件(Controlled Component)」
🧠 什麼是「受控」與「非受控」?
「受控元件(Controlled Component)」和「非受控元件(Uncontrolled Component)」是 React 處理表單輸入時非常重要的兩種方式。
很多新手在學表單時常聽到這兩個詞,但一開始會很困惑:到底差在哪?該用哪一種?
我們來用白話+範例+比較表,幫你一次搞懂 👇
類型 | 誰控制輸入框的內容? |
---|---|
受控元件 | React 的 state 在控制 → 你說了算 |
非受控元件 | DOM 本身在控制 → 它自己記住自己的值 |
✅ 受控元件(Controlled Component)
你用 useState()
來記住欄位內容,並且把輸入值「綁」在 state 上。
✨ 寫法範例:
function ControlledInput() {
const [text, setText] = useState('');
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<p>你輸入的是:{text}</p>
</div>
);
}
📌 特徵:
value
是由 state 控制- 使用者一輸入文字,就觸發
onChange
→ 更新 state → 畫面重新 render - 輸入值完全綁在 React 裡面
❎ 非受控元件(Uncontrolled Component)
你不使用 state 來記住輸入值,而是讓 DOM 自己記。
✨ 寫法範例:
function UncontrolledInput() {
const inputRef = useRef(null);
const handleClick = () => {
alert('你輸入的是:' + inputRef.current.value);
};
return (
<div>
<input ref={inputRef} />
<button onClick={handleClick}>顯示輸入值</button>
</div>
);
}
📌 特徵:
- 沒有
useState
input
自己記住自己的值- 想取得值時,要去
ref
裡找 DOM →.current.value
🆚 比較一下:受控 vs 非受控
項目 | 受控元件(Controlled) | 非受控元件(Uncontrolled) |
---|---|---|
資料存在哪? | React 的 state | HTML 的 input DOM 自己記 |
是否能即時取得值? | ✅ 即時:state 就是最新值 | ❌ 需透過 ref 存取 |
用法是否直覺? | ✅ 整合 React 思維 | ⚠️ 需要操作 DOM |
適合處理大量表單? | ✅ 可整體管理、驗證、送出 | ❌ 不利集中管理 |
常見應用場景 | 表單輸入、登入註冊、搜尋欄 | 舊表單整合、第三方 UI 組件、簡單用途等 |
🤔 那到底該用哪一種?
✔️ 大部分情況都建議用「受控元件」
因為:
- 資料在 React 裡,容易集中管理
- 驗證、格式化、即時提示都好處理
- 搭配
useState()
、useEffect()
可以做很多互動行為
但也有少數情況會選擇非受控元件,例如:
- 表單很簡單,只需要送出時才要拿值
- 整合第三方套件(像是日期選擇器、WYSIWYG 編輯器)時
- 性能需求:大量欄位不想每輸入一字就 render
🎯 總結一句話:
受控元件是 React 官方推薦、也最常見的做法,因為它讓資料流更清楚、邏輯更集中。
非受控元件則是提供給特定場景的輕量解法,當你不需要即時管理資料時才會用。
更新 state 的注意事項:讓 React 正確「感知變化」
React 的設計核心就是「資料驅動畫面」。也就是說:
只要你的資料(state)有變,React 就會幫你自動更新畫面。
但前提是:你必須透過正確的方式來改變 state。
千萬不要直接改變 state!
先看一個錯誤的寫法:
let [count, setCount] = useState(0);
// ❌ 錯誤:直接改變變數值
count = count + 1;
這雖然在 JavaScript 裡是合法的寫法,但 React 不會知道你偷偷改了 count
!
所以畫面 不會更新,你會以為值變了,但畫面卻沒跟著變。
✅ 正確寫法:一定要用 setX()
函式
setCount(count + 1);
這樣寫的時候,React 才會知道:
- 哦~你要把
count
改成新的數字了 - 我要觸發重新 render,把最新的
count
顯示出來
這就是為什麼我們常說:
只有透過
setState
(或setCount
)等函式,React 才能追蹤變化並自動更新畫面。
如果更新依賴「舊的值」怎麼辦?
有時候,你想根據「上一次的 state 值」來決定下一個值,例如連點按鈕多次:
setCount(count + 1); // ❌ 有可能 count 還沒更新,連點會失準
這時候推薦用函式寫法:
setCount(prevCount => prevCount + 1); // ✅
這樣寫的好處是:
- React 會幫你確保
prevCount
一定是最新的值 - 尤其在非同步、連續更新的情況下更準確(例如連點按鈕、批次更新)
每個元件都有自己的 state:資料各自獨立,不會干擾
很多初學者會問:
如果我有兩個元件都用了
useState()
,它們的資料會不會打架、互相干擾?
答案是:不會!
來看這個範例 👇
function App() {
return (
<div>
<Counter />
<Counter />
</div>
);
}
這裡我們渲染了兩個 <Counter />
元件,每個裡面都寫了:
const [count, setCount] = useState(0);
這時候,這兩個 count
是 完全獨立、互不相干的。
你按第一個按鈕,只會改變它自己的數字,第二個不受影響。
這就是 React 的一個超棒特性:
每個元件就像自己的小宇宙,擁有獨立的 state。
這樣的封裝性,讓元件可以安全地被重複使用、組合成更大的畫面,而不用擔心資料亂掉。
什麼情況下該用 state?三個超簡單判斷問題
你在寫元件的時候,可能會問自己:「這筆資料我要用 state 嗎?」
以下是 React 團隊推薦的三個判斷問題:
這筆資料會改變嗎?
- ✅ 會:例如輸入值、按鈕點擊、開關狀態
- ❌ 不會:像是靜態標題、icon 圖示 → 可以直接寫死,不需要 state
這筆資料是這個元件自己在用的嗎?
- ✅ 是:像是 input 的內容、是否展開細節區塊
- ❌ 否:是別人控制的(例如 props 傳進來),就不該自己設 state
畫面需要根據這筆資料來更新嗎?
- ✅ 需要:像是輸入內容顯示在畫面上、某些區塊要不要顯示
- ❌ 不需要:那這資料也許不該是 state,可能只是變數
如果三個問題的答案都是「是」,那就:用 useState()
吧!
const [yourValue, setYourValue] = useState(initialValue);
你就能好好管理這筆資料,還能享受 React 幫你更新畫面的便利!
結語:state 是互動性的關鍵!
你現在已經學會:
state
是元件自己的資料記憶體- 使用
useState()
可以輕鬆新增、更新、讀取資料 - 當 state 改變,畫面也會跟著自動更新
state
是讓 React 元件「活起來」的靈魂,未來還會學到更進階的狀態管理工具(像是 useReducer
、Context API
),但打好 useState()
的基礎,是 React 新手的必經之路!