什麼是 prop drilling?當資料傳遞變成麻煩

更新日期: 2025 年 4 月 14 日

你學會了 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,中間每一層都要幫忙傳:

  1. Appname 傳給 Parent
  2. Parent 再把 name 傳給 Child
  3. 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 拿資料,而不用透過 ParentChild 轉傳 props。

// 概念範例(詳細教學會在下一章介紹)
<Greeting /> 就可以直接取得 name,而不需透過多層轉傳

我們會在下一篇文章中,詳細介紹 Context 的用法,讓你解決 prop drilling 的痛點!

📝 小結:先理解問題,再來找工具解法

📌 重點✅ 說明
prop drilling 是什麼?當資料需要傳到深層元件,必須經過每一層轉傳 props 的現象
為什麼困擾?中間元件變得複雜、可讀性降低、維護困難
解法方向?使用 Context API 等方法共享資料,減少不必要的傳遞

下一篇,我們將會帶你進入 Context API 的世界,一步步教你怎麼從零建立、提供與消費全域資料,不再被 props 綁架!

Similar Posts

發佈留言

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