什麼是 prop drilling?當資料傳遞變成麻煩
更新日期: 2025 年 4 月 14 日
《React 元件基礎介紹》:
- React 的開發邏輯:為什麼現代前端要用元件?
- React 元件是什麼?畫面積木的基本單位
- 什麼是元件樹?理解元件的父子與巢狀結構
- 撰寫第一個 React 元件:函式型元件入門
- 如何規劃與拆分元件?從 UI 切割建立模組思維
- 元件之間如何傳資料?Props 教學入門
- 初學者也能懂!React children 全解析與實戰教學
- React 元件可以有自己的資料?state 基礎觀念與使用
- 什麼是 prop drilling?當資料傳遞變成麻煩
- Context API 是什麼?解決 prop drilling 的痛點
- 比 Context 更好用的選擇?認識第三方狀態管理工具
在閱讀本文前,建議先閱讀《JSX 基礎介紹》
你學會了 props
,知道怎麼讓父元件把資料「丟給」子元件,但有沒有遇過這種情況:
想把一筆資料給最底層的元件,結果中間的每一層元件都要幫忙「轉傳」一次?
這就是 prop drilling 的問題。
在這篇文章中,我們會:
- 解釋什麼是 prop drilling
- 實作一個例子,看看問題怎麼出現
- 分析 prop drilling 為什麼令人頭痛
- 簡單預告解法:Context API
什麼是 prop drilling?
定義:層層傳遞 props 的現象
在 React 中,我們常用 props
(屬性)來讓元件之間傳遞資料。
這種方式很直觀 —— 父元件傳資料給子元件,子元件再根據資料顯示內容或做出反應。
不過,當元件變得比較多、層級變得比較深時,你可能會遇到這樣的情況:
「我只想把一個變數傳給最底層的元件,但中間經過的每一層都得寫一段
props
,幫我『轉交』資料。」
這種情況,就叫做 prop drilling。
簡單來說,prop drilling 是:
- 資料的來源在上層元件,但
- 資料的使用者在深層子元件
- 中間的每一層元件都必須「接收並轉交」該資料
- 即使這些中間元件完全不需要用到那筆資料
這就像一封信,原本是你寫給住在三樓的朋友,但這棟樓沒有電梯,你得拜託一樓住戶交給二樓、再由二樓交給三樓。每一層都得幫忙傳一次。
雖然他們根本不需要打開信、也不關心內容,但他們不得不參與整個傳遞過程。
為什麼會出現這種情況?
在 React 中,資料的流動是「單向的」(unidirectional data flow):
flowchart TD A[App 元件<br />包含 userData 狀態] --> |傳遞 userData| B[父層元件] B --> |傳遞 userData<br />但不使用| C[中間元件1] C --> |傳遞 userData<br />但不使用| D[中間元件2] D --> |傳遞 userData<br />但不使用| E[中間元件3] E --> |傳遞 userData<br />但不使用| F[深層子元件<br />真正使用 userData] style A fill:#f9d77e,stroke:#f0932b style F fill:#badc58,stroke:#6ab04c classDef propDrilling fill:#ff7979,stroke:#eb4d4b class B,C,D,E propDrilling
React 沒有像變數那樣「全域共用」的資料,所有資料都得靠 props 一層一層往下送。
所以當資料需求橫跨多層元件時,就容易出現 prop drilling。
實作範例:從一層變三層的麻煩
為了更直觀理解什麼是 prop drilling,我們來看一段簡單的程式碼。
原本這樣(只有一層,傳資料超簡單)
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
function App() {
return <Greeting name="Alice" />;
}
這段程式碼的邏輯非常直接:
App
是最上層元件,負責傳入資料name="Alice"
Greeting
是子元件,直接用這筆資料顯示出歡迎文字
執行結果會是:
<h1>Hello, Alice!</h1>
一層傳遞、一層使用,清楚又簡潔。
現在我們加了兩層巢狀元件,麻煩開始了
假設你現在有更多的 UI 結構,把 Greeting
包在兩層元件裡:
function App() {
return <Parent name="Alice" />;
}
function Parent({ name }) {
return <Child name={name} />;
}
function Child({ name }) {
return <Greeting name={name} />;
}
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
這段看起來沒什麼變化,畫面輸出也還是一樣的:
<h1>Hello, Alice!</h1>
但你有沒有發現一個問題?
傳遞鏈條變長,維護難度也變高
現在這個 name
必須從 App
一路「傳手」到 Greeting
,中間每一層都要幫忙傳:
App
把name
傳給Parent
Parent
再把name
傳給Child
Child
再傳給Greeting
然而:
Parent
根本不使用name
Child
也不使用name
- 他們只是「中繼站」
也就是說,即使你只是在 UI 上多包了幾層結構,這筆資料也被迫經過每一層的 props
傳遞。這讓原本簡單的程式碼變得又長又容易出錯。
想像更真實的情境
現在你有一個大型應用程式,資料要從第 1 層傳到第 6 層,這中間的第 2~5 層都必須一層一層幫忙轉送 props
。
那如果第 3 層元件改名了?或你想把第 4 層抽出去成另一個共用元件?你就必須檢查整條傳遞鏈,手動調整 props
,否則畫面就會出錯。
這種「層層傳送」的設計,很容易因為一點點變動就導致資料斷鏈,對於大型專案來說,維護與擴充都是一大痛點。
延伸問題:程式可讀性與開發效率下降
除了增加維護成本,這種方式還會帶來其他問題:
- 🔧 開發效率變慢:每加一層元件,就得多寫一段
props
傳遞 - 👀 程式碼難以理解:未來的開發者必須追著
props
往上找資料來源 - 🐛 容易引入 bug:只要某一層漏傳或寫錯,就會讓資料消失,畫面出錯
Prop Drilling 為什麼麻煩?
乍看之下,props 傳遞好像沒什麼問題,但當應用變得稍微複雜、元件變多時,prop drilling 就成了維護上的一大阻力。
讓我們來逐點說明,為什麼 prop drilling 會讓人頭痛:
中間元件變得「虛胖」:沒有使用資料,卻要攜帶資料
在 prop drilling 的情境下,很多中間層元件其實根本不需要使用資料,卻不得不接受 props,然後再原封不動傳給下一層。
久而久之,你的元件會變得像這樣:
function MiddleLayer({ user, theme, language, onLogout, children }) {
return <div>{children}</div>;
}
這些 props
不是 MiddleLayer 自己要用的,而是為了下面某個孫子元件存在的,這就讓原本應該單純的元件「膨脹」了起來,負責了太多不相干的責任。
💬 專業術語中,我們會說這種元件「違反了單一職責原則」(Single Responsibility Principle)。
重構困難:一動全身,修改起來很痛苦
當你想:
- 改變資料的命名(例如把
userName
改成displayName
) - 改變資料的來源(從另一個 hook 來)
- 改變元件的巢狀結構(例如新增一層、合併兩層)
你都得一層一層修改 props,從頂層一路改到最底層,像這樣:
<App userName="Alice" />
→ <Parent userName="Alice" />
→ <Child userName="Alice" />
→ <Greeting userName="Alice" />
任何一層忘記改名或漏掉,都會讓資料傳遞出錯。這在大型專案中尤其容易發生,也非常耗時。
可讀性變差:找資料來源像偵探辦案
對維護者或新進開發者來說,閱讀一段 prop drilling 的程式碼就像在追查「這筆資料是哪裡來的?」。
你必須從最底層元件,一路往上爬去找是哪一層傳進來的,可能還會遇到中途被重新命名的情況,增加理解成本:
// 最底層
<Greeting displayName={displayName} />
// 中間層
<Child displayName={userName} />
// 頂層
<Parent userName="Alice" />
整個追蹤過程不僅費時,還非常容易出錯,debug 時特別惱人。
實務建議:哪些情況容易遇到 Prop Drilling?
當資料不只用在一個元件,而是需要「跨越多層結構」時,就容易陷入 prop drilling 的困境。以下是幾個常見場景:
顯示使用者資訊(像是登入者姓名或頭像)
這些資訊通常在 App 一啟動時就取得,但會出現在很多地方:
- Header 導覽列
- 側邊欄
- 設定頁面
為了把這筆資料傳到各個元件,就會出現層層傳遞的情況。
深層元件需要觸發上層的函式(例如登出、切換語言)
假設你有個按鈕在第四層元件,要呼叫頂層的 logout()
函式,那你就得一路這樣傳下來:
<Layer1 onLogout={logout} />
→ <Layer2 onLogout={onLogout} />
→ <Layer3 onLogout={onLogout} />
→ <LogoutButton onLogout={onLogout} />
中間這三層只是「轉交」,實際不負責登出。
共用狀態:主題顏色(Theme)、語系(Language)、裝置模式(Dark Mode)
這些屬性通常會影響整個 UI,卻會散落在多個深層元件中。若都用 props 傳遞,很快會陷入一團亂。
解法預告:Context API 是救星
為了解決 prop drilling 的問題,React 提供了一個內建工具叫做 Context API。
Context 允許你在不透過每層 props 的情況下,讓元件之間「共享資料」。
舉例來說,如果你用 Context 傳遞 name
,就可以讓 Greeting
直接從 Context 拿資料,而不用透過 Parent
和 Child
轉傳 props。
// 概念範例(詳細教學會在下一章介紹)
<Greeting /> 就可以直接取得 name,而不需透過多層轉傳
我們會在下一篇文章中,詳細介紹 Context 的用法,讓你解決 prop drilling 的痛點!
📝 小結:先理解問題,再來找工具解法
📌 重點 | ✅ 說明 |
---|---|
prop drilling 是什麼? | 當資料需要傳到深層元件,必須經過每一層轉傳 props 的現象 |
為什麼困擾? | 中間元件變得複雜、可讀性降低、維護困難 |
解法方向? | 使用 Context API 等方法共享資料,減少不必要的傳遞 |
下一篇,我們將會帶你進入 Context API 的世界,一步步教你怎麼從零建立、提供與消費全域資料,不再被 props 綁架!