useState 入門:讓元件擁有自己的資料

更新日期: 2025 年 4 月 16 日

在學會 React 元件後,你可能會發現一個問題:當畫面重新渲染時,元件內的變數好像會「歸零」?

舉個例子,如果你寫了一個 let count = 0,但當使用者點按按鈕後畫面更新,count 還是從 0 開始。

那該怎麼辦呢?這就是 React 提供的第一個 Hook —— useState 派上用場的地方!


什麼是 state?為什麼我們需要它?

state 是什麼?

在 React 中,state(狀態)是一種能讓元件「記住資料」的機制

當你寫一個按鈕、輸入框或其他互動元件時,你會希望使用者的動作能反映在畫面上,這時就需要「記住」某些資料 —— 而這些資料,就是 state。

可以這樣想:

State 就像是一個元件的「內部記憶」,記錄了現在的狀態是什麼,讓元件知道該怎麼顯示畫面。

✅ 舉例來說:

  • 使用者點了一下「+1」按鈕,畫面中的數字要加一
    count 是一個 state,記錄目前的數字
  • 使用者切換了燈泡的開關,畫面要顯示開或關的圖示
    isOn 是一個 state,記錄燈泡的狀態
  • 使用者在表單輸入名字,你要即時顯示他輸入的內容
    name 是一個 state,記錄使用者輸入的值

為什麼不能用一般變數?

剛開始學 React 時,你可能會想用 JavaScript 的方式寫:

let count = 0;

function MyComponent() {
  function handleClick() {
    count += 1;
    console.log(count);
  }

  return <button onClick={handleClick}>點我</button>;
}

乍看之下好像可以,但你會發現 畫面不會更新,即使 count 的值改變了。

這是因為:

React 是「聲明式」的 UI 框架,只有透過它的狀態機制(像 useState)變更資料,React 才知道「要重新渲染畫面」。

🧾 什麼是「聲明式(Declarative)」的 UI 框架?

✅ 定義:你只需要告訴系統「要呈現什麼」,而不是「怎麼一步步去做到」

👀 比喻說明:像是點菜 vs 自己下廚

  • 聲明式(Declarative):你去餐廳點一份牛排
    → 你只需要「描述想要的結果」(我想吃五分熟牛排配黑胡椒醬)
    → 廚師會幫你搞定怎麼做、用什麼順序、開多大火、什麼時候翻面
  • 命令式(Imperative):你自己下廚做牛排
    → 你必須自己「一步步告訴系統該做什麼」
    → 拿鍋子、開火、熱油、放牛排、煎 3 分鐘翻面、撒調味料…

🖼 在 UI 編程中呢?

🟥 傳統命令式框架(例如 jQuery)

你會寫出一堆這樣的流程控制語句:

const list = document.createElement('ul');
for (let i = 0; i < items.length; i++) {
  const li = document.createElement('li');
  li.textContent = items[i];
  list.appendChild(li);
}
container.appendChild(list);

👉 你得親自「一步步告訴電腦」:

  • 怎麼建立元素
  • 加入到哪個節點
  • 寫入什麼資料
🟩 React 的聲明式寫法

React 則讓你直接描述畫面「長什麼樣」就好

function ItemList({ items }) {
  return (
    <ul>
      {items.map((item) => <li key={item}>{item}</li>)}
    </ul>
  );
}

👉 重點是:

你不需要關心「要建立多少個 <li> 元素」、「要用哪個 DOM 方法 append」

你只需要說:「我想要一個清單,裡面有這些項目」

React 就會自動幫你比對差異、更新畫面

🎯 為什麼這樣比較好?
  • ✅ 程式碼更簡潔、語意更清楚
  • ✅ 不容易出錯(不用手動操作 DOM)
  • ✅ 更容易維護和修改(改 UI 結構就像改 HTML 一樣直覺)
  • ✅ React 幫你處理底層複雜的流程(虛擬 DOM、重新渲染邏輯)
🧠 小結:聲明式 vs 命令式
觀念命令式(Imperative)聲明式(Declarative)
你要做什麼?一步步告訴電腦怎麼做事情描述你想要的結果,讓框架幫你處理
程式風格流程控制、多行邏輯、手動更新結構清晰、描述式、框架自動處理
實作方式操作 DOM、自己追蹤狀態、管理節點使用 state + JSX 描述畫面
React 屬於哪種?❌ 否✅ 是

React 的設計理念正是:

把 UI 當成「狀態的函數」來寫,而不是一步步「組出來」的結果

如果你有用過 Excel,也可以這樣理解:

  • 在 Excel 裡,=A1 + B1 是不是自動根據資料改變?
    ➤ 這就是一種聲明式的邏輯。

✅ 具體來說,問題有三個:

  1. 一般變數不會觸發重新渲染
    當你改變 let count = 0 這種變數的值,React 並不知道要重新渲染元件,因此畫面不會更新。
  2. 變數每次渲染都會重設
    在函式型元件中,每次畫面重新渲染,整個函式會重新執行一次。
    所以 let count = 0 這樣的寫法,每次都會從 0 開始,相當於「失憶」。
  3. 資料狀態與畫面不同步
    UI 的畫面應該反映目前的資料狀態,但如果你用的是外部變數、全域變數或 function 內部的變數,很容易造成資料與畫面「不同步」的 bug。

✅ 用 useState 就對了!

為了解決這些問題,React 提供了 useState() 這個 Hook:

const [count, setCount] = useState(0);

這樣 React 就能:

  • 在畫面中保留 count 的值(即使重新渲染也不會被洗掉)
  • 當你呼叫 setCount() 時,自動更新畫面

這也是為什麼 React 建議你用 useState 管理資料,而不是用普通的變數。

🧠 小總結

方式會記住資料嗎?會觸發畫面更新嗎?建議用法
let count = 0❌ 每次都被重設❌ 不會更新畫面❌ 不建議
useState(0)✅ 會記住資料✅ 自動更新畫面✅ 正確做法

useState 的基本用法

當你希望 React 元件能記住某個資料,並在資料改變時自動更新畫面,就需要使用 useState() 這個 Hook。

基本語法與結構

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>目前計數:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

✅ 解說:

語法部分解釋
useState(0)這是初始化狀態,初始值是 0,也可以是字串、布林值、物件或陣列等任何類型
[count, setCount]透過「解構賦值」拿到兩個東西:count 是目前的值,setCount 是修改它的函式
setCount(count + 1)呼叫這個函式會「更新狀態」,並自動觸發畫面重新渲染
onClick={() => setCount(...)}當按鈕被點擊時,就會執行這段更新狀態的邏輯

React 每次重新 render 時,會根據最新的 state 來更新畫面,而不需要你手動操作 DOM。

❗ 小提醒:不要直接改變 state!

你可能會直覺地寫:

count = count + 1; // ❌ 錯誤!這樣不會讓 React 重新渲染畫面

這樣的寫法只是在函式內部改變變數,React 完全不知道發生了什麼事

✅ 正確做法是:

setCount(count + 1); // ✅ React 會接收到這次變動,觸發畫面更新

因為 React 是透過 setCount() 來追蹤狀態改變並重新 render 的,如果你繞過它去改變值,React 就不會理你。

延伸閱讀:React 核心觀念解密:setCount 究竟是什麼?為何它能同時接收數值與函式?

多個 state 該怎麼寫?

在一個元件中,我們常常不只需要一筆資料。

例如一個燈泡元件,可能要同時記住「有沒有開」跟「顏色是什麼」。

那我們該怎麼做?

你可以宣告多個 useState,每筆狀態獨立管理,各自更新,非常靈活。

function LightSwitch() {
  const [isOn, setIsOn] = useState(false);        // 控制燈泡開關
  const [color, setColor] = useState('yellow');   // 控制燈泡顏色

  return (
    <div>
      <p>狀態:{isOn ? '💡 開' : '🌑 關'},顏色:{color}</p>
      <button onClick={() => setIsOn(!isOn)}>切換開關</button>
      <button onClick={() => setColor('blue')}>變藍色</button>
    </div>
  );
}

✅ 為什麼不用一個 object 管理多個 state?

你當然可以這樣寫:

const [light, setLight] = useState({ isOn: false, color: 'yellow' });

但這樣一來,每次修改都得自己合併狀態:

setLight(prev => ({ ...prev, isOn: !prev.isOn }));

雖然這在某些情境下是有用的,但對新手來說比較複雜。

代碼拆解說明

🔍 原始程式碼是這樣:

setLight(prev => ({ ...prev, isOn: !prev.isOn }));

🧠 這是什麼場景?

假設你有一個 state 是物件:

const [light, setLight] = useState({
  isOn: false,
  color: 'yellow',
});

你想要只改變 isOn 的值(例如把 false 變 true),但保留其他欄位(像 color)不變。

✅ 拆解這一行的意思

🔹 setLight(...)

這是在更新 light 這個狀態。

🔹 (prev => ...)

這是函式的語法,意思是你要根據「上一個 state 的值」來產生新的值。

這樣寫的好處是:即使 React 還沒更新完畫面,你也能拿到最新的值來做修改。

prev 就是前一個狀態的值,也就是:

{
  isOn: false,
  color: 'yellow'
}
🔹 { ...prev }

這是「展開運算子(spread operator)」的用法。

意思是:把原本的 prev 裡面的資料都複製過來。像這樣:

{
  isOn: false,
  color: 'yellow'
}
🔹 isOn: !prev.isOn

這行是覆蓋原本的 isOn

  • !prev.isOn 的意思是「把原本的布林值反過來」
  • 如果原本是 false,就變成 true
  • 如果原本是 true,就變成 false

📌 小技巧補充:布林值取反

像開關這種布林值切換,可以這樣寫:

setIsOn(prev => !prev);

這樣可以確保你拿到的是「最新狀態」,即使有多次連續的操作,也不會出錯。

📦 結果會變成:

{
  isOn: true,          // ← 改變了
  color: 'yellow'      // ← 保持不變
}

然後 React 會用這個新物件來更新畫面。

✅ 等同於這種寫法,但更安全:

也可以這樣寫,只是比較麻煩:

setLight({
  isOn: !light.isOn,
  color: light.color,
});

但這樣有個風險:light 這個變數可能不是最新的資料(因為 React 是非同步更新的)。
所以建議用 函式寫法 setX(prev => ...) 更安全。

🧠 小總結

這行程式碼:

setLight(prev => ({ ...prev, isOn: !prev.isOn }));

意思是:

「拿到上一個狀態,把所有欄位複製下來,但把 isOn 的值反過來,然後更新整個狀態。」

React 的哲學是:

「一筆資料,就用一個 state 來管」,這樣程式碼更清楚、修改也更方便。

小結

觀念正確做法
宣告一個狀態值const [x, setX] = useState(initialValue)
更新狀態,讓畫面跟著改變setX(newValue)
多個資料狀態怎麼寫宣告多個 useState()
不要這樣寫x = newValue(不會更新畫面)

實作練習

點擊計數器

來練習一個簡單的小功能:每按一下按鈕,數字就加 1。

function ClickCounter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>點擊了 {count}</h2>
      <button onClick={() => setCount(count + 1)}>再點一下</button>
    </div>
  );
}

👉 試著自己加上「重置按鈕」看看!


開關燈泡

再來玩個互動的範例:點一下開關,改變燈泡狀態。

function LightBulb() {
  const [isOn, setIsOn] = useState(false);

  return (
    <div>
      <p>💡 燈泡目前:{isOn ? '💡 開著' : '🌑 關著'}</p>
      <button onClick={() => setIsOn(!isOn)}>
        {isOn ? '關燈' : '開燈'}
      </button>
    </div>
  );
}

這樣就可以讓你的元件記得它的狀態,並隨著點擊而變化畫面。

額外技巧:依賴前一個狀態

如果你需要根據「前一個狀態」來更新資料,建議使用函式形式:

setCount(prev => prev + 1);

這樣能確保你拿到的是最新值,避免非同步更新的 bug。

常見錯誤與陷阱

多次 setState 不會立即反映結果

setCount(count + 1);
setCount(count + 1); // 這兩行其實不會 +2,因為 count 值還沒更新

正確方式:

setCount(prev => prev + 1);
setCount(prev => prev + 1); // 這樣才會變成 +2

state 更新後不會立即反映在 console.log

setCount(5);
console.log(count); // ❌ 還是舊值

為畫面還沒更新完,想看到新值請用 useEffect 等待 render。


    結論

    恭喜你學會 useState

    接下來,你會發現「當資料改變時要觸發一些動作」是很常見的需求,這時就需要用到另一個 Hook —— useEffect,它能幫你處理像是:

    • 載入 API 資料
    • 啟動倒數計時器
    • 清理資源

    下一篇我們就要開始學習這個超重要的 Hook!

    Similar Posts

    發佈留言

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