在學程式的過程中,你可能會在很多地方看到「Hook」這個詞:
- Git 有 Git Hooks
- WordPress 有 Action Hooks 和 Filter Hooks
- 很多框架都有 Lifecycle Hooks
- 作業系統有 System Hooks
看起來到處都有 Hook,但它到底是什麼意思?
其實 Hook 是一個通用的程式設計概念,從 1990 年代的 Windows 作業系統就開始使用了。不管你現在學的是什麼語言或框架,理解 Hook 的核心概念,以後遇到任何「某某 Hook」都能快速上手。
這篇文章會用最白話的方式,帶你從零開始認識 Hook。
Hook 到底是什麼?
先聊聊「Hook」這個詞
你可能會好奇:為什麼叫「Hook(掛勾)」?
在台灣,講到「掛勾」我們通常會想到牆上那種用來掛東西的勾子。但在英文裡,Hook 更常見的意象其實是「魚鉤」🪝。
理解這點,整個概念就通了。
用釣魚來解釋
想像你在河邊釣魚:
魚鉤的特性:
- 放進水流中 → 不會擋住河水,水還是照流
- 攔截你要的魚 → 特定的魚咬餌時,你就能把牠拉上來處理
- 可以隨時放、隨時收 → 想釣就放勾,不想釣就收起來
在程式的世界裡,Hook 就像這個魚鉤:
- 程式流程 = 河水
- 資料/事件 = 游過的魚
- Hook = 你放進去的魚鉤
- Hook 函式 = 魚上鉤後,你要怎麼處理牠
在程式碼裡是什麼樣子?
原本程式的流程:
步驟 A → 步驟 B → 步驟 C
放了 Hook 之後:
步驟 A → 步驟 B → 🪝【你的程式碼在這裡攔截】→ 步驟 C程式還是照常跑,但在你放 Hook 的地方,會順便執行你寫的程式碼。
魚上鉤之後呢?
你可能會想:「魚被釣走了,不就消失了嗎?那資料不就不見了?」
好問題!其實 Hook 攔截到資料後,不一定要把魚「帶走」,通常有這幾種處理方式:
| 處理方式 | 釣魚比喻 | 程式世界 |
|---|---|---|
| 看一眼就放走 | 看看是什麼魚,拍個照,然後放回去 | 監聽事件、記錄 log |
| 修改一下再放走 | 幫魚掛個標籤或做個記號,再放回河裡繼續游 | Filter Hook,修改資料後讓它繼續傳遞 |
| 不讓牠繼續游 | 這條魚有問題,攔截下來不放行 | 阻止事件,中斷原本的流程 |
大多數情況下,Hook 就像「河流中的檢查站」——資料游過來、停下來讓你檢查或處理一下,然後繼續往下游。
一句話定義
Hook 就是:在程式執行流程中「放一個魚鉤」,攔截你感興趣的資料或事件,然後做你想做的事。
這也是為什麼英文文件常用 “intercept”(攔截) 這個詞來描述 Hook 的行為——就像魚鉤攔截游過的魚一樣。
為什麼需要 Hook?
一個實際的問題
假設你在用一套別人開發的部落格系統,你想要加一個功能:
每次有人發表新文章時,自動發一封 Email 通知管理員
問題來了:你不能直接改別人的原始碼
為什麼不能改?
- 改了之後,系統更新你的修改就會被覆蓋
- 你可能改壞其他功能
- 很難維護
Hook 怎麼解決這個問題?
回到釣魚的比喻:
你不能把整條河改道(改原始碼),但你可以在河邊放一個魚鉤(Hook),等特定的魚游過來時(發文事件),把牠攔截下來處理(發 Email)。
好的程式設計會預留「可以放魚鉤的位置」,讓你這樣做:
原本的發文流程:
使用者點發布 → 儲存到資料庫 → 顯示成功
放了 Hook 之後:
使用者點發布 → 儲存到資料庫 → 🪝【你的程式:發 Email】→ 顯示成功你只要把「發 Email」的程式碼「勾」在那個點上,完全不用動到原本的程式碼。
Hook 的三大好處
| 好處 | 釣魚比喻 | 程式世界 |
|---|---|---|
| 不用改原始碼 | 不用改變河流,只要放魚鉤 | 原本的程式怎麼更新都不影響你 |
| 功能可插拔 | 想釣就放鉤,不想釣就收起來 | 想用就掛上,不想用就拔掉 |
| 好維護 | 你的魚鉤、你的事 | 你的程式碼和原本的分開管理 |
Hook 的常見類型
類型一:Before(之前)和 After(之後)
這是最常見的 Hook 類型,就像在河流的「上游」或「下游」放魚鉤。
🪝【Before Hook】← 在動作「之前」攔截
↓
原本的動作
↓
🪝【After Hook】← 在動作「之後」攔截用釣魚比喻:
- Before Hook:在魚游進某個區域「之前」就先攔截
- After Hook:讓魚先游過去,「之後」再攔截
程式實例:
- Before:儲存資料「之前」,先檢查資料格式對不對
- After:儲存資料「之後」,發一封確認信給使用者
類型二:Filter(過濾/修改)
這種 Hook 讓你可以「攔截並修改」正在傳遞的資料。就像抓到魚之後,幫牠清洗一下再放回去。
資料游過來 → 🪝【Filter Hook:攔截、修改】→ 修改過的資料繼續往下游程式實例:
- 使用者輸入文字,經過 Filter 把髒話換成 ***
- 使用者上傳圖片,經過 Filter 自動壓縮大小
Hook 在各種地方的應用
Hook 這個概念到處都在用,讓我們看看幾個常見的地方:
作業系統層級
這是 Hook 最早的應用場景,從 Windows 3.0(1990 年)就有了。
Windows 系統 Hook 的例子:
| Hook 類型 | 做什麼用的 |
|---|---|
| Keyboard Hook | 攔截鍵盤輸入 |
| Mouse Hook | 攔截滑鼠動作 |
| Message Hook | 攔截系統訊息 |
哪些軟體在用?
- 螢幕錄影軟體:攔截畫面來錄影
- 密碼管理器:攔截輸入框來自動填入密碼
- 快捷鍵工具:攔截特定按鍵組合來執行功能
- 防毒軟體:攔截可疑的系統行為
版本控制:Git Hooks
如果你有用 Git 來管理程式碼,Git 本身就有內建 Hook 機制。
Hook 檔案放在專案的 .git/hooks/ 資料夾
常用的 Git Hooks:
| Hook 名稱 | 什麼時候觸發 | 可以拿來做什麼 |
|---|---|---|
pre-commit | 執行 commit 之前 | 自動檢查程式碼風格 |
commit-msg | 寫完 commit 訊息後 | 檢查訊息格式 |
pre-push | 執行 push 之前 | 自動跑測試 |
post-merge | 執行 merge 之後 | 自動安裝新套件 |
實際例子:
#!/bin/sh
# 檔案:.git/hooks/pre-commit
# 這段程式會在每次 commit 之前自動執行
echo "正在檢查程式碼..."
# 跑程式碼檢查
npm run lint
# 如果檢查失敗,就阻止 commit
if [ $? -ne 0 ]; then
echo "❌ 程式碼檢查沒通過,請修正後再 commit"
exit 1
fi
echo "✅ 檢查通過!"網站系統:WordPress Hooks
WordPress 是全世界最多人用的網站系統,它的 Hook 機制設計得非常完整。
Action Hook(動作掛勾):在某個時機點「做某件事」
// 告訴 WordPress:每次發布文章後,執行 my_function
add_action('publish_post', 'my_function');
function my_function($post_id) {
// 這裡的程式碼會在每次發文後自動執行
send_email_to_admin($post_id);
}Filter Hook(過濾掛勾):修改某個資料
// 告訴 WordPress:每次顯示文章內容時,先經過 add_footer
add_filter('the_content', 'add_footer');
function add_footer($content) {
// 在每篇文章最後加上一段文字
return $content . '<p>感謝閱讀!</p>';
}後端框架的 Hook
很多後端框架也有類似的機制,有時候叫 Hook,有時候叫 Middleware 或 Lifecycle。
資料庫操作的 Hook(以 Sequelize + PostgreSQL 為例):
const { Sequelize, DataTypes } = require('sequelize');
const sequelize = new Sequelize('postgresql://...');
const User = sequelize.define('User', {
name: DataTypes.STRING,
password: DataTypes.STRING
}, {
hooks: {
// Before Hook:儲存「之前」自動加密密碼
beforeCreate: async (user, options) => {
user.password = await hashPassword(user.password);
console.log('密碼已加密!');
},
// After Hook:刪除「之後」記錄 log
afterDestroy: (user, options) => {
console.log('使用者已刪除:', user.name);
}
}
});網頁請求的 Hook(Middleware):
// 每個請求進來都會先經過這裡
app.use((req, res, next) => {
console.log('收到請求:', req.url);
console.log('時間:', new Date());
next(); // 繼續往下執行
});前端框架的 Lifecycle Hooks
前端框架通常會提供「生命週期 Hook」,讓你在元件的不同階段執行程式碼。
什麼是元件的生命週期?
在前端框架裡,畫面是由一個一個「元件」組成的(你可以把元件想像成一塊一塊的積木)。每個元件從出現到消失,會經歷幾個階段,這就叫做「生命週期」:
用演員比喻:
把元件想像成一個演員:
| 生命階段 | 演員比喻 | 你可以在這時做什麼 |
|---|---|---|
| 建立 | 演員準備上台 | 初始化資料、設定預設值 |
| 掛載 | 演員登場,觀眾看到了 | 去 API 抓資料、啟動計時器 |
| 更新 | 演員換裝、換台詞 | 根據新資料重新計算、更新畫面 |
| 移除 | 演員下台,表演結束 | 清理計時器、取消訂閱、釋放資源 |
為什麼需要 Lifecycle Hooks?
想像一個情境:你有一個顯示即時股價的元件
- 掛載時:要啟動一個計時器,每 5 秒去抓最新股價
- 移除時:如果使用者切換到別的頁面,元件消失了,但計時器還在跑,就會浪費資源甚至出錯
有了 Lifecycle Hook,你可以:
元件掛載 → 🪝 Hook:啟動計時器,開始抓股價
↓
元件移除 → 🪝 Hook:清掉計時器,停止抓股價這樣就能確保資源被正確管理,不會有「元件消失了但計時器還在跑」的問題。
容易搞混的相似概念
學 Hook 的時候,你可能會遇到一些看起來很像的名詞。讓我們繼續用河流釣魚的比喻來釐清它們的差別。
Hook vs Callback(回呼函式)
這兩個概念非常相似,很多人會搞混。
| Hook | Callback | |
|---|---|---|
| 概念 | 系統預留好擴充點,你把程式碼「掛」上去 | 你把函式「傳」給別人,讓別人在適當時機呼叫 |
| 誰主動 | 你等系統來觸發 | 你主動傳遞函式 |
| 使用方式 | 通常用「註冊」的方式 | 通常當作參數傳入 |
用河流比喻:
- Hook:河邊本來就有設計好的「釣魚平台」🎣,你只要去那邊放下魚鉤,等魚來就好
- Callback:你自己帶了一個魚鉤,交給船長說「如果有魚經過,用這個鉤幫我抓」
程式碼對比:
// Callback:你主動把函式(魚鉤)傳進去
fetchData(url, function(data) {
console.log('資料回來了', data);
});
// Hook:系統有預留釣魚點,你去那邊註冊
WordPress: add_action('publish_post', myFunction);
Git: 把腳本放到 .git/hooks/pre-commit
Sequelize: hooks: { beforeCreate: myFunction }結論:其實很像!Hook 內部通常就是用 Callback 來實現的。主要差別在於「誰設計了這個機制」——Hook 是河邊預先蓋好的釣魚平台,Callback 是你自己帶魚鉤請別人幫忙。
Hook vs Middleware(中介軟體)
這兩個在 Web 開發中很常見,概念也很接近。
| Hook | Middleware | |
|---|---|---|
| 概念 | 在特定時機插入程式碼 | 在請求處理流程中插入程式碼 |
| 使用範圍 | 廣泛,各種場景都有 | 主要用在 Web 框架 |
| 執行方式 | 可能只在特定事件觸發 | 通常每個請求都會經過 |
用河流比喻:
- Hook:在河邊放魚鉤 🪝,只有特定的魚(事件)咬餌時才會觸發
- Middleware:在河流中蓋了好幾道「水閘門」🚧,每一滴水(每個請求)都必須依序通過每一道閘門
Hook:
河水流動 → 大部分魚游過 → 🪝 只有特定的魚被攔截 → 河水繼續流
Middleware:
河水流動 → 🚧閘門1 → 🚧閘門2 → 🚧閘門3 → 每滴水都要過每道閘門
(紀錄) (過濾) (檢查)Middleware 的特色:每個請求都要經過
// Express Middleware:每個請求都會依序經過每道閘門
app.use(紀錄Log); // 第一道閘門
app.use(檢查登入); // 第二道閘門
app.use(驗證權限); // 第三道閘門
// ↓ 然後才到達目的地Hook 的特色:只攔截特定事件
// Hook:只有特定的魚(事件)才會觸發
beforeSave → 只有「儲存」這條魚游過來時才攔截
afterDelete → 只有「刪除」這條魚游過來時才攔截結論:Middleware 像是河流中的「水閘門」,每滴水都要經過;Hook 像是河邊的「魚鉤」,只攔截特定的魚。Middleware 可以說是 Hook 概念在「Web 請求處理」上的特殊應用。
Hook vs Event(事件)
這兩個看起來都是「某件事發生時執行程式碼」,但有個關鍵差別。
| Hook | Event | |
|---|---|---|
| 能力 | 可以攔截、修改、甚至阻止原本的動作 | 通常只是通知你有事發生 |
| 介入程度 | 可以深度介入流程 | 比較像旁觀者 |
| 能否阻止 | 通常可以 | 通常不行,事情已經發生了 |
用河流比喻:
- Hook(魚鉤):你把魚鉤放進河裡 🪝,魚游過來時你可以:
- 看一眼就放走
- 幫牠做記號再放走
- 不讓牠繼續往下游(攔截)
- Event(事件):你在河邊裝了一個「感應器」📡,魚游過去的時候會發出「叮!」的通知聲。但魚已經游過去了,你只是被通知,無法攔截牠。
Hook:
魚游過來 → 🪝 攔截!→ 你決定要怎麼處理 → 放走 or 不讓牠過
Event:
魚游過來 → 魚游過去了 → 📡「叮!有魚經過」→ 你只是被通知程式碼對比:
// Event:魚已經游過去了,你只是被通知
button.addEventListener('click', () => {
console.log('按鈕被點了!'); // 點擊已經發生,你只是收到通知
});
// Hook:魚還沒游過去,你可以決定要不要放行
form.onsubmit = (e) => {
if (!isValid) {
e.preventDefault(); // 不讓這條魚過去!
return false;
}
};更明顯的例子:
// Event 風格:魚已經游走了,你只是被通知
database.on('saved', (data) => {
console.log('資料已儲存', data); // 已經存了,你只是被通知
});
// Hook 風格:魚還在你手上,你可以決定怎麼處理
database.beforeSave((data) => {
if (data.price < 0) {
throw new Error('價格不能是負數!'); // 不讓這條魚過去!
}
data.updatedAt = new Date(); // 幫魚做個記號
return data; // 放走,讓牠繼續游
});結論:Event 是河邊的「感應器」,告訴你有魚經過;Hook 是「魚鉤」,讓你可以攔截、修改、甚至阻止魚繼續往下游。
總結比較
| 名詞 | 河流比喻 | 關鍵特色 |
|---|---|---|
| Hook | 魚鉤 🪝 | 攔截特定的魚,可修改、可阻止 |
| Callback | 你帶來請別人用的魚鉤 | 你主動提供工具 |
| Middleware | 河中的水閘門 🚧 | 每滴水都要經過,串聯執行 |
| Event | 河邊的感應器 📡 | 通知有魚經過,但無法攔截 |
其實這些概念有很多重疊的地方,不用太糾結它們的邊界。重要的是理解它們各自強調的重點!
不同領域的 Hook 命名習慣
認識這些命名慣例,遇到新框架時可以更快理解:
| 領域/工具 | 命名方式 | 例子 |
|---|---|---|
| Git | 時機 + 動作 | pre-commit、post-merge |
| WordPress | add_action、add_filter | add_action('init', ...) |
| 後端框架 | pre/post + 動作 | pre('save')、post('remove') |
| 前端框架 | 生命週期名稱 | mounted、created、destroyed |
| 作業系統 | 事件類型 + Hook | KeyboardHook、MouseHook |
自己設計 Hook 的簡單範例
如果你想在自己的程式裡實現 Hook 機制,概念其實很簡單:
// 一個超簡單的 Hook 系統
// 1. 建立一個地方存放所有掛上來的函式
const hooks = {
beforeSave: [],
afterSave: []
};
// 2. 提供「掛載」的方法
function addHook(hookName, fn) {
hooks[hookName].push(fn);
}
// 3. 在適當的時機「觸發」Hook
function saveData(data) {
// 觸發 beforeSave hook
hooks.beforeSave.forEach(fn => fn(data));
// 執行真正的儲存
console.log('儲存資料:', data);
// 觸發 afterSave hook
hooks.afterSave.forEach(fn => fn(data));
}
// 使用方式:
addHook('beforeSave', (data) => {
console.log('準備儲存...');
});
addHook('afterSave', (data) => {
console.log('儲存完成,發送通知!');
});
saveData({ name: 'Alice' });
// 輸出:
// 準備儲存...
// 儲存資料:{ name: 'Alice' }
// 儲存完成,發送通知!總結
核心觀念
記住釣魚的比喻,Hook 就很好理解了:
| 釣魚 | 程式 |
|---|---|
| 河流 | 程式執行流程 |
| 游過的魚 | 資料、事件、訊息 |
| 魚鉤 | Hook |
| 抓到魚後的處理 | Hook 函式 |
- Hook 是通用概念:不屬於任何特定語言或框架,到處都在用
- Hook 的本質:在程式流程中「放一個魚鉤」,攔截你感興趣的東西
- Hook 的價值:不用改河道(原始碼),只要放魚鉤就能做事
Hook 出現的地方
作業系統 → Windows/Linux 系統 Hook
版本控制 → Git Hooks
網站系統 → WordPress Hooks
後端框架 → Middleware、Lifecycle Hooks
前端框架 → Lifecycle Hooks遇到新的 Hook 時,問自己三個問題
當你在學新工具遇到「某某 Hook」時,問自己:
- 什麼時候可以放鉤? → 了解觸發時機
- 能攔截到什麼? → 了解能處理的資料或事件
- 能不能阻止魚繼續游? → 了解能否中斷原本的流程
搞懂這三點,你就掌握那個 Hook 了!
💡 小提醒:不同框架對 Hook 的實作細節可能不同,但核心概念都是一樣的。學會這個概念,以後遇到任何 Hook 都不怕!