React 元件可以有自己的資料?state 基礎觀念與使用

更新日期: 2025 年 4 月 14 日

你在學習 React 的過程中,可能已經習慣用 props 傳資料給元件,但你可能也會想問:

「有些資料是元件內部自己要記住的,不能靠父母提供,怎麼辦?」

例如:點擊按鈕要切換畫面狀態、輸入欄位要記住使用者輸入的文字、計時器要記住秒數⋯⋯這些資料,是元件自己產生、自己使用的,不適合從外面傳進來。

這時候,React 就提供了 state(狀態) 這個超好用的機制,讓你的元件像有「小腦袋」一樣能記住資料!


什麼是 state?

定義:元件內部的「變動資料儲存空間」

在 React 中,每一個元件都是一個可以根據資料狀態改變畫面的獨立單位。

而這些「資料狀態」就被稱為 state

簡單來說:

state 是一種用來記住元件內部資料的機制,它的值可以被更新,而且一更新就會自動觸發畫面重新渲染(re-render)

這讓使用者在和畫面互動時(例如:按按鈕、輸入文字、點擊切換)可以即時看到最新的反應,而不需要你手動操作 DOM 來改變畫面,這正是 React 的強大之處。

把 state 想成元件的「小腦袋」

每個元件好像都擁有自己的「小腦袋」,它可以自己記住某些資料,不需要靠別人幫它記。

例如:

  • 表單中的輸入內容
  • 計數器目前的數字
  • 一個列表目前有幾個項目
  • 一個 modal 是否正在開啟
  • 使用者目前點選了哪個頁籤

這些都是元件自己要記住的資料,它們會隨著使用者的操作而改變,也就是我們說的「變動資料」。

這種場景就適合使用 state 來處理。

當你更新 state 時,會發生什麼事?

當你使用 React 提供的 setState 函式(或 Hook 的 setX),改變了某個 state 值時:

  1. React 會自動偵測到這個元件的資料變了
  2. 它會觸發這個元件重新渲染
  3. 畫面就會即時更新,反映最新的資料

這就是為什麼我們說 React 是「聲明式」框架:你只要負責設定資料,React 幫你更新畫面。

state 跟 props 差在哪?

state 常常和 props 被放在一起講,因為它們都是「資料來源」,但其實概念完全不同

角色資料來源誰可以修改適合用途
props外部元件傳進來不能修改接收父元件提供的資料,像是參數
state自己元件內部可以修改元件自己記錄的資料,例如輸入值、UI 狀態等

📖 更生活化的比喻

角色說明
props像是父母交代的小任務,不能隨便改,乖乖照做
state像是自己的筆記本,想記什麼就記什麼

所以當你遇到一筆資料:

  • 是「別人給你的」 → 用 props
  • 是「自己產生的、自己要改的」 → 用 state

判斷是否使用 state 的小技巧

問自己這三個問題:

  1. 這筆資料是由元件自己管理的嗎?
  2. 這筆資料會不會因為互動而改變
  3. 資料變動時,畫面需要跟著更新嗎?

如果答案是「是」,那這筆資料就適合放在 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 會自動幫你完成這些事:

  1. 更新資料
  2. 重新執行元件(重新畫畫面)
  3. 顯示最新的值

不用自己操控畫面、不用直接動 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 會幫你做以下幾件事:

  1. 把新值存起來
  2. 觸發這個元件重新執行(re-render)
  3. 更新畫面,顯示最新狀態

💬 白話比喻一下:

你可以把 useState() 想成一個「小工具包」,每次你呼叫它,它就幫你生一組資料組合:

function useState(initValue) {
  let currentValue = initValue;

  function setValue(newValue) {
    currentValue = newValue;
    renderAgain(); // React 幫你重新 render 畫面
  }

  return [currentValue, setValue];
}

雖然實際實作比這複雜很多,但概念上你可以這樣理解。

🔁 結論一句話:

setCountuseState() 回傳的「更新用函式」,你取什麼名字都可以,但建議用 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 是怎麼運作的?流程如下:

  1. 使用者在 input 輸入文字
  2. 觸發 onChange 事件,執行 setText(e.target.value)
  3. text 的值被更新(state 改變)
  4. React 偵測到 state 改變 → 自動重新執行這個元件
  5. 畫面重新 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 的 stateHTML 的 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 元件「活起來」的靈魂,未來還會學到更進階的狀態管理工具(像是 useReducerContext API),但打好 useState() 的基礎,是 React 新手的必經之路!

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *