告別 Class!React Hooks 的誕生背景與優點
更新日期: 2025 年 4 月 15 日
在學習 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()
:觸發狀態更新與重新渲染- 生命週期方法(如
componentDidMount
、componentDidUpdate
):在特定時機點,執行副作用邏輯
舉例來說,下面是一個最基本的 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
是一個非常容易出錯的地雷區。
舉例來說,如果你忘了在 constructor
中 bind(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,就能使用
state
、lifecycle
- 將邏輯封裝成可重用的函式
- 更容易拆分與組合功能,符合 React 組件化設計的核心理念
為什麼叫做「Hook」?
「Hook」這個詞,在程式設計裡的意思就像是:
💡「系統開了一個小門,讓你可以在某個特定時機點,把自己的程式碼『掛上去』,加入自訂邏輯,但又不需要修改原本的系統流程。」
想像你有一台跑得很順的機器(系統),你不能亂動它的內部結構,但你可以在它運作到某個「階段」時,掛上你自己的東西──像是在機器的齒輪上「掛一個鈴鐺」,讓它轉到那裡時會叮噹響。
這個「掛鈴鐺」的動作,就是「Hook」。
🧠 生活中的 Hook 類比
幫你舉幾個更貼近生活的例子:
生活場景 | Hook 類比 |
---|---|
電燈開關旁邊加個自動感應器 | 電燈本來自己會亮,但你「鉤住」它的開啟動作,加上自己的邏輯:感應到人時才開燈 |
Word 加上外掛,自動儲存時幫你備份 | Word 的「儲存」流程本來只是存檔,你加一段「自動上傳雲端」的邏輯,就是用了一個 hook |
洗衣機進水後自動加柔軟精 | 系統本來只負責進水,但你加了一個「檢測水位→加入柔軟精」的步驟,就是一個 hook |
程式裡的 Hook 是什麼?
在「電腦科學」和「軟體設計」中,Hook 就是系統留給你的「掛鉤點」:
「系統會在某個時間點,主動通知你,讓你有機會插入自己的邏輯去做一些事。」
這很像你訂閱了一個「通知」,只要某個事件發生(像是畫面渲染完、按鈕點擊、資料載入完成),你就可以做你自己的事。
而且你不需要知道系統內部怎麼寫,你只要告訴它:
「欸,到這一步時,記得讓我插個隊,我要加點東西。」
這種「在不改原本程式的情況下,加入自訂邏輯」的設計方式,就叫做 Hook 機制(hooking mechanism),也可以稱為「擴充點(extension point)」。
React 為什麼用 Hook 這個詞?
React 團隊使用「Hook」這個名稱,是有原因的,而且這個詞完美地反映出它在元件中的角色。
在 React 中,Hook 的意思就是:
「React 開放了一些『鉤子』,讓你可以在函式型元件的執行流程中,掛上你想要的功能,而 React 會在適當時機自動執行它們。」
用一句話形容就是:
➡️ 你想要什麼功能,就把它「掛」上去,React 幫你做好剩下的事。
🧩 Hook 的用途比喻
你可以把每個 Hook 想像成是一個「功能掛勾(plugin hook)」,像這樣:
你想實現的功能 | 掛上的 Hook |
---|---|
想讓元件「記住資料」? | useState |
想在畫面更新後「執行某些事」? | useEffect |
想存資料但不要影響畫面? | useRef |
想讓全站共用主題或語言? | useContext |
🪝 那根「桿子」是什麼意思?我們為什麼要掛 Hook?
在圖中,那根橫桿(竿子)代表的是「React 函式元件的執行流程」,也就是:
React 在畫面渲染前後,依照一套既定的順序在執行你的元件。
你可以把它想像成:
- 一個固定跑的傳送帶
- 或是一條不能改變的作業線
- React 每次 render 時都會順著這條線跑流程:建立元件 → render 畫面 → 處理副作用 → 等待互動 → 卸載元件…
🎯 Hook 就是你可以「掛」在這條流程上的擴充功能
而那些藍色的小掛勾(useState
、useEffect
、useRef
…)就是:
💡你在不改變原本流程的情況下,選擇性地掛上一些功能
你可以這樣理解:
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,像這樣:
Hook 名稱 | 掛上的功能 |
---|---|
useState() | 為元件加上可追蹤的狀態資料 |
useEffect() | 加入副作用邏輯(如訂閱、API 請求、監聽) |
useRef() | 記錄資料、操作 DOM,不會導致重新渲染 |
useContext() | 接入全域資料共享系統,避免層層傳 props |
useReducer() | 處理複雜邏輯或大型狀態(像 Redux 的簡化版) |
你只需要在元件裡呼叫這些函式,React 就會自動把它整合到元件的生命周期中,讓功能穩定執行。
Hook 的優點整理:React 開發的新時代
React Hooks 是 React 團隊為了解決 class 元件痛點而設計的功能,它不只是語法上的糖衣,而是整個開發體驗的革新。
以下是 Hooks 幾個讓人愛不釋手的優點:
語法簡潔,學習曲線大幅降低
在學習 class 元件時,你得理解 constructor
、super(props)
、this.state
、this.setState
、this.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 只能出現在兩種地方:
- React 函式元件內
- 自訂的 Hook(以
use
開頭)中
你不能在普通函式、條件邏輯、class 中使用 Hook,否則會報錯:
React Hook "useState" cannot be called at this time.
➡️ 如果你要重構邏輯,請寫成「自訂 Hook」然後在元件中呼叫。
小提醒
✅ 可以這樣用 | ⚠️ 不要這樣用 |
---|---|
useState() 在元件頂層 | 在 if 裡呼叫 useState() |
useEffect() 放在元件中 | 在普通函式中呼叫 Hook |
把邏輯抽成 useXXX 的自訂 Hook | 在 class 元件中呼叫 Hook(會爆炸) |
小結:為什麼我們應該學 Hook?
如果你正在學習 React,與其花時間在 class 上,不如直接進入 Hook 的世界。因為:
- React 官方推薦未來以 Hook 為主流
- 社群、套件、生態系也以 Hook 為基礎
- 專案開發更簡潔、更好維護
未來的文章,我們會依序介紹各種常用 Hook,包括 useState
、useEffect
、useRef
、useContext
、useReducer
等等,讓你逐步掌握 React 的現代開發思維。