告別 Class!React Hooks 的誕生背景與優點

Published April 14, 2025 by 徐培鈞
JavaScript

在學習 React 的過程中,很多初學者都會卡在「Class Component」這一關。

state、生命週期方法、this 綁定問題……常常讓人看得一頭霧水。

不只新手覺得困難,就連有經驗的工程師也不時被這些語法搞得暈頭轉向。

React 團隊注意到這個痛點,於是在 React 16.8 推出了 Hooks(鉤子)機制,一種全新的方式,讓你不用寫 class,就能使用 state 與其他 React 特性

本篇文章將帶你一起揭開 Hooks 的誕生故事,了解它為什麼誕生、解決了什麼問題,以及為什麼在現代 React 專案中幾乎都是「函式型元件 + Hook」的組合!


Class Component 的痛點

在 React 16.8 以前,如果我們想讓元件有自己的資料(state)、在畫面渲染後執行副作用(如 API 載入、設定事件監聽等),就必須使用 Class Component

這是因為只有 Class Component 提供了:

  • this.state:用來儲存元件的狀態資料
  • this.setState():觸發狀態更新與重新渲染
  • 生命週期方法(如 componentDidMountcomponentDidUpdate):在特定時機點,執行副作用邏輯

舉例來說,下面是一個最基本的 Class 寫法:點擊按鈕讓計數器加 1:

class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 })
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }
}

這段看似簡單的程式碼,卻已經暴露出多個問題點。讓我們逐一拆解說明。

語法冗長又難懂

使用 Class Component 會讓初學者,在一開始就要同時學習許多不屬於 React 核心概念的語法,例如:

  • constructor():必須寫來初始化元件,並正確地呼叫 super(props)
  • this.state:用物件格式定義初始狀態
  • this.setState():狀態更新不能直接修改值,而是必須透過這個方法
  • bind(this):事件處理函式預設不會綁定 this,必須手動綁定才能使用 this.setState

這些「非直覺」的語法,不但容易打錯,也讓學習門檻變高,學 React 變成學 JavaScript 的 class 語法大補帖。

this 綁定常常出錯

在 class 中,this 是一個非常容易出錯的地雷區。

舉例來說,如果你忘了在 constructorbind(this),或者用了箭頭函式卻沒注意上下文,執行時就會遇到這種錯誤:

TypeError: Cannot read property 'setState' of undefined

這是因為事件處理函式中的 this 並不會自動指向該元件實例,而會變成 undefined 或其他物件,導致你在呼叫 this.setState() 時報錯。

這樣的錯誤雖然常見,卻很難 debug,讓開發者經常陷入「為什麼我寫的明明一樣卻出錯」的沼澤中。

邏輯分散不好管理

一個元件如果需要處理複雜的行為(例如 API 載入、訂閱 WebSocket、清除計時器),就必須分別寫在對應的生命週期函式中:

  • componentDidMount:元件初次掛載時執行
  • componentDidUpdate:每次重新渲染時執行
  • componentWillUnmount:元件即將被移除時執行清理邏輯

這會導致一個功能的實作邏輯,被拆散在多個函式之間,像這樣:

componentDidMount() {
  this.fetchData()
  window.addEventListener('scroll', this.handleScroll)
}

componentWillUnmount() {
  window.removeEventListener('scroll', this.handleScroll)
}

componentDidUpdate(prevProps) {
  if (prevProps.id !== this.props.id) {
    this.fetchData()
  }
}

這讓元件難以閱讀與維護,你很難快速理解「一個功能完整的生命週期」,除非把所有相關函式全部對照著看。

更糟的是,如果一個元件負責很多功能,這些邏輯就會交錯在一起,不只雜亂,還容易引發 bug。

小結

Class Component 在 React 初期確實提供了強大功能,但隨著應用越來越複雜,它的缺點也越來越明顯:

說明constructor、super、this 綁定等學習門檻高
說明this 錯誤最常見、debug 困難
說明一個功能分散在多個生命週期方法中,難以維護

這就是為什麼 React 團隊想要「跳脫 class 的框架」,發明了更簡潔、彈性的 Hooks 機制。


Hook 的出現:一場革命性的改變

React 團隊為了解決 Class Component 的各種問題(語法複雜、this 綁定地獄、邏輯分散難維護),在 React 16.8 正式推出了一套全新的設計機制:Hooks(鉤子)

這個機制讓我們:

  • 不用寫 class,就能使用 statelifecycle
  • 將邏輯封裝成可重用的函式
  • 更容易拆分與組合功能,符合 React 組件化設計的核心理念

為什麼叫做「Hook」?

「Hook」這個詞,在程式設計裡的意思就像是:

💡「系統開了一個小門,讓你可以在某個特定時機點,把自己的程式碼『掛上去』,加入自訂邏輯,但又不需要修改原本的系統流程。」

想像你有一台跑得很順的機器(系統),你不能亂動它的內部結構,但你可以在它運作到某個「階段」時,掛上你自己的東西──像是在機器的齒輪上「掛一個鈴鐺」,讓它轉到那裡時會叮噹響。

這個「掛鈴鐺」的動作,就是「Hook」。

🧠 生活中的 Hook 類比

幫你舉幾個更貼近生活的例子:

Hook 類比電燈本來自己會亮,但你「鉤住」它的開啟動作,加上自己的邏輯:感應到人時才開燈
Hook 類比Word 的「儲存」流程本來只是存檔,你加一段「自動上傳雲端」的邏輯,就是用了一個 hook
Hook 類比系統本來只負責進水,但你加了一個「檢測水位→加入柔軟精」的步驟,就是一個 hook

程式裡的 Hook 是什麼?

在「電腦科學」和「軟體設計」中,Hook 就是系統留給你的「掛鉤點」:

「系統會在某個時間點,主動通知你,讓你有機會插入自己的邏輯去做一些事。」

這很像你訂閱了一個「通知」,只要某個事件發生(像是畫面渲染完、按鈕點擊、資料載入完成),你就可以做你自己的事。

而且你不需要知道系統內部怎麼寫,你只要告訴它:

「欸,到這一步時,記得讓我插個隊,我要加點東西。」

這種「在不改原本程式的情況下,加入自訂邏輯」的設計方式,就叫做 Hook 機制(hooking mechanism),也可以稱為「擴充點(extension point)」。

React 為什麼用 Hook 這個詞?

React 團隊使用「Hook」這個名稱,是有原因的,而且這個詞完美地反映出它在元件中的角色。

在 React 中,Hook 的意思就是:

「React 開放了一些『鉤子』,讓你可以在函式型元件的執行流程中掛上你想要的功能,而 React 會在適當時機自動執行它們。」

用一句話形容就是:

➡️ 你想要什麼功能,就把它「掛」上去,React 幫你做好剩下的事。

🧩 Hook 的用途比喻

你可以把每個 Hook 想像成是一個「功能掛勾(plugin hook)」,像這樣:

掛上的 HookuseState
掛上的 HookuseEffect
掛上的 HookuseRef
掛上的 HookuseContext
🪝 那根「桿子」是什麼意思?我們為什麼要掛 Hook?

在圖中,那根橫桿(竿子)代表的是「React 函式元件的執行流程」,也就是:

React 在畫面渲染前後,依照一套既定的順序在執行你的元件。

你可以把它想像成:

  • 一個固定跑的傳送帶
  • 或是一條不能改變的作業線
  • React 每次 render 時都會順著這條線跑流程:建立元件 → render 畫面 → 處理副作用 → 等待互動 → 卸載元件…
🎯 Hook 就是你可以「掛」在這條流程上的擴充功能

而那些藍色的小掛勾(useStateuseEffectuseRef…)就是:

💡你在不改變原本流程的情況下,選擇性地掛上一些功能

你可以這樣理解:

  • useState:我需要讓這個元件有記憶功能 → 掛上!
  • useEffect:我需要在畫面出現或更新後做點事情 → 掛上!
  • useRef:我需要記住一些值,但不需要影響畫面 → 掛上!

這些 Hook 只是「加值邏輯」,不會干擾 React 本身的執行流程

你不掛,流程照跑;你掛了,React 會在適當時機幫你執行、更新、清理。

📌 實際範例說明:useState

讓我們來看一段最簡單的使用 Hook 的 React 程式碼:

import { useState } from 'react'

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

  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

這一行:

const [count, setCount] = useState(0)

其實就是你對 React 說:

「嘿,我這個元件想要『有記憶』,從 0 開始,幫我記住它,並在更新時自動重渲。」

React 就會:

  • 幫你保留 count 的值(不管畫面怎麼重新 render,它都會存在)
  • 提供一個 setCount 函式讓你可以更新它
  • 當你呼叫 setCount 時,自動讓畫面重新顯示新的值

這整個流程,你完全不需要手動操作 DOM 或管理狀態儲存邏輯,只要用 Hook 一掛上,React 就替你包辦。

🤝 React Hook 跟電腦科學中的 Hook:一樣的概念,不同的體驗

✅ 相同的地方(核心概念一致)

React Hook 繼承了傳統電腦科學中的 Hook 概念:

「讓開發者不用碰系統核心,就能在指定時機插入自訂邏輯。」

React 透過這些 Hook API,提供一種「可插入但不侵入」的設計方式,讓你:

  • 不需要寫 class
  • 不需要手動處理元件生命週期
  • 不需要額外建立外部狀態管理機制

你只要在頂層「掛上」想要的功能,它就會在 React 的生命週期中自動運作。

🧼 不同的地方(React 把 Hook 變得更簡單)

傳統的 Hook(像作業系統或遊戲引擎中的 Hook)常常需要處理以下挑戰:

  • 綁定與解除邏輯要自己管理
  • 執行時機要自己判斷
  • 若用不好,可能會破壞系統穩定性

但在 React 中,Hook 被設計成乾淨、穩定、封裝良好的函式 API,像這樣:

掛上的功能為元件加上可追蹤的狀態資料
掛上的功能加入副作用邏輯(如訂閱、API 請求、監聽)
掛上的功能記錄資料、操作 DOM,不會導致重新渲染
掛上的功能接入全域資料共享系統,避免層層傳 props
掛上的功能處理複雜邏輯或大型狀態(像 Redux 的簡化版)

你只需要在元件裡呼叫這些函式,React 就會自動把它整合到元件的生命周期中,讓功能穩定執行。


Hook 的優點整理:React 開發的新時代

React Hooks 是 React 團隊為了解決 class 元件痛點而設計的功能,它不只是語法上的糖衣,而是整個開發體驗的革新。

以下是 Hooks 幾個讓人愛不釋手的優點:

語法簡潔,學習曲線大幅降低

在學習 class 元件時,你得理解 constructorsuper(props)this.statethis.setStatethis.bind(this)……
只為了讓元件記住一個數字或執行一次 API?

Hooks 大幅簡化了這一切,像這樣:

function Counter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(count + 1)}>{count}</button>
}

你只需要記住:

📌 狀態用 useState,副作用用 useEffect,其餘看需求加。

不需要任何 class 語法、也不會被 this 綁來綁去。

➡️ 初學者更容易入門,老手也更快開發。

重複邏輯可以抽成「自訂 Hook」

在以前的 class 元件中,若有幾個元件都需要做「表單驗證」、「API 請求」、「scroll 偵測」等相似邏輯,通常只能靠:

  • 複製貼上
  • HOC(高階元件)
  • render props(函式作為子元素)

這些方法寫起來複雜又繞口。

但在 Hook 的世界中,你只要把邏輯封裝成一個「自訂 Hook」,就像寫工具函式一樣簡單:

function useForm() {
  const [values, setValues] = useState({})
  const handleChange = e => setValues({ ...values, [e.target.name]: e.target.value })
  return { values, handleChange }
}

然後在任一元件中掛上它:

const { values, handleChange } = useForm()

➡️ 邏輯可重用、結構清晰、維護更簡單!

更容易拆分邏輯(分工清楚不打架)

以前如果你想在 componentDidMount 裡做很多事,例如:

  • 叫 API 拿資料
  • 設定計時器
  • 加動畫效果

這些邏輯會全部擠在一個函式裡,久了就變成「大雜燴」。

componentDidMount() {
  this.fetchData()
  this.setupAnimation()
  this.startTimer()
}

但 Hook 可以讓你每段邏輯獨立管理:

useEffect(() => {
  fetchData()
}, [])

useEffect(() => {
  setupAnimation()
}, [])

useEffect(() => {
  startTimer()
  return () => clearTimer()
}, [])

➡️ 每個 useEffect 專心做一件事,邏輯拆得更乾淨。

更適合組合使用(超有彈性)

Hook 之間是可以「互相組合」的,你可以把多個 Hook 混搭成新的邏輯,就像樂高積木一樣。

比如說,你可以用:

  • useState 管理欄位值
  • useEffect 監聽欄位變化
  • useRef 存取錯誤提示 DOM
  • 自訂一個 useValidation() 管理表單錯誤

組合起來變成一個完整的表單邏輯,而每一塊都可以獨立重用!

➡️ 這種組合性(composability)是 React 開發的一大特色,而 Hook 正好完美實現它。


使用 Hook 的規則與限制

雖然 Hook 很強大,但它也不是無所不能。為了確保 React 能正確追蹤 Hook 的執行順序與狀態更新,有幾條黃金規則你一定要記住:

規則一:只能在「函式元件的最上層」呼叫 Hook

這是為了讓 React 能依據「呼叫順序」記住每一個 Hook 對應的狀態。

✅ 正確:

function MyComponent() {
  const [count, setCount] = useState(0)
}

❌ 錯誤(不要在條件、迴圈、巢狀函式中呼叫):

if (isLogin) {
  const [user, setUser] = useState(null) // ❌ 錯誤!
}

➡️ React 是靠「順序」對應每個 Hook 的位置,如果你改變呼叫順序,它就會抓錯。

規則二:只能在「React 函式中」使用 Hook

Hook 只能出現在兩種地方:

  1. React 函式元件內
  2. 自訂的 Hook(以 use 開頭)中

你不能在普通函式、條件邏輯、class 中使用 Hook,否則會報錯:

React Hook "useState" cannot be called at this time.

➡️ 如果你要重構邏輯,請寫成「自訂 Hook」然後在元件中呼叫。

小提醒

⚠️ 不要這樣用在 if 裡呼叫 useState()
⚠️ 不要這樣用在普通函式中呼叫 Hook
⚠️ 不要這樣用在 class 元件中呼叫 Hook(會爆炸)

小結:為什麼我們應該學 Hook?

如果你正在學習 React,與其花時間在 class 上,不如直接進入 Hook 的世界。因為:

  • React 官方推薦未來以 Hook 為主流
  • 社群、套件、生態系也以 Hook 為基礎
  • 專案開發更簡潔、更好維護

未來的文章,我們會依序介紹各種常用 Hook,包括 useStateuseEffectuseRefuseContextuseReducer 等等,讓你逐步掌握 React 的現代開發思維。