iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
自我挑戰組

React 入門到實作與除錯|30 天哩ㄟ刻系列 第 20 篇

Day 20 : 注意還動,Context 跨層傳遞資料

  • 分享至 

  • xImage
  •  

公告貼了,群組也說了,就是有人不看

今天目標

  1. 認識 Context
  2. 比較 props 與 Context
  3. 共享詳細與精簡的顯示設定
  4. 執行觀察

認識 Context

更新規則集中,資料要怎麼傳

用了 reducer 整理訂單更新,把新增、刪除、改杯數和改規格的規則放在一起,事件函式送出 action,reducer 計算下一份訂單,清單與檢視再透過 props 收到資料。

今天加入一個顯示設定:詳細時顯示規格與金額,精簡時只顯示名稱和杯數。

觀察一下資料傳遞路徑,從清單拆出顯示單筆內容的內層元件,這份設定會:

`DrinkOrder` -> `OrderList` -> `OrderItemDetails`

但是 OrderList 只是排列清單,不需要使用這份設定,這種資料逐層經過只負責轉交的元件,就會遇到常說的 prop drilling。

Context 讓外層元件提供資料,由下方需要的元件直接讀取,中間層不必另外接收和轉交這份設定。

React:Passing Data Deeply with Context

誰提供,誰讀取

共享的值在這個專案中,我們使用 "detailed" 和 "compact" 兩種,先認識三個角色:

名稱 在這次練習裡做什麼
createContext 建立提供端與讀取端共同使用的 Context
Provider 用 value 提供這個範圍的顯示設定
useContext 讓元件讀取並訂閱這份設定

Provider 就是資料提供者。元件讀取時,會往元件樹上方找最近的同一個 Context Provider;找不到,才使用建立 Context 時指定的預設值。

React:useContext

設定由 state 保存

顯示模式放在 DrinkOrder 的 useState,按鈕呼叫 setDisplayMode 更新,Context 負責將目前的值傳下去,讀取端再決定要顯示哪些文字。

比較 props 與 Context

共同設定可以跨層讀取

  • props:資料來源直接寫在元件呼叫處,容易看出誰收到什麼
  • Context:則能減少中間層轉交設定,但閱讀程式要往上找 Provider 才知道實際讀到哪個值。

以現在的飲料訂單資料為例,會這樣安排:

資料 傳遞方式
每筆訂單 item、目前檢視的 order 使用 props,明確指定元件顯示哪一筆
加杯、刪除、套用規格等回呼 沿用 props,交給外層元件處理
共同的顯示模式 displayMode 使用 Context,讓需要的內層元件讀取
未套用的規格草稿 保留在 OrderEditor 的局部 state

依資料用途選擇

當多個較深的元件需要同一份設定時,Context 比較能減少轉交的工作,或是透過 children 組合元件,縮短需要傳遞資料的層數。

React:Before you use context

用 Context 共享顯示設定

建立 Context

匯入 createContext 與 useContext,保留原本的 Hook 與型別,在所有 import 後、元件函式外建立:

type DisplayMode = "detailed" | "compact";

const DisplayModeContext = createContext<DisplayMode>("detailed");

DisplayMode 限制設定只能是詳細或精簡,"detailed" 是找不到 Provider 時的預設值,Context 放在元件外建立,讓提供端與讀取端使用同一個物件。

React:createContext

保存目前設定,再提供給內層元件

在 DrinkOrder 的 Hook 區塊加入:

const [displayMode, setDisplayMode] = useState<DisplayMode>("detailed");

接著在頁首後、表單前新增「詳細顯示」與「精簡顯示」按鈕,分別呼叫 setDisplayMode("detailed") 和 setDisplayMode("compact")。

最後在 DrinkOrder 原本的 <main> 外包上 <DisplayModeContext value={displayMode}>,保留裡面的完整畫面。這樣清單和檢視都位於 Provider 下方,可以讀取它提供的值。

React 19 可以直接用 Context 當 Provider。
較早的教學會寫成 <DisplayModeContext.Provider value={displayMode}>
React:Context Provider

讓真正顯示內容的元件讀取設定

將清單每筆訂單的名稱、規格和金額拆成 OrderItemDetails ,透過 props 接收 item,顯示模式則在元件最上層讀取:

const displayMode = useContext(DisplayModeContext);

再以 displayMode === "detailed" 決定是否顯示規格與單筆金額,名稱和杯數則一直保留。

在 OrderList 的每個 <li> 裡,用 <OrderItemDetails item={item} /> 替換原本三行文字,保留所有操作按鈕與 key={item.id}。OrderList 不必接收 displayMode,也不用呼叫 useContext。

OrderPreview 同樣讀取 Context,再依模式決定是否顯示規格與小計,收到的訂單與回呼 props 繼續保留。

看看設定經過哪裡

現在元件關係如下:

DrinkOrder:useState 保存 displayMode
└─ DisplayModeContext value={displayMode}
   ├─ OrderList:排列清單,不轉交顯示設定
   │  └─ OrderItemDetails:useContext 讀取設定
   ├─ OrderPreview:useContext 讀取設定
   └─ OrderEditor:保留原本的局部草稿

按下精簡顯示,state 更新,Provider 提供新的值,兩個讀取端就能依設定更新內容。

執行觀察

準備兩筆不同規格的訂單

存檔後重新載入,加入以下兩筆並選取 A:

  • 紅茶 A:2 杯、半糖少冰、單價 30 元。
  • 綠茶 B:1 杯、全糖正常冰、單價 25 元。

初始合計是 2 筆、3 杯、85 元。

切換顯示,訂單內容保留

按精簡顯示,清單與檢視會省略規格和單筆金額,保留名稱、杯數與按鈕,清單上方的總額仍是 85 元,草稿編輯區也照常顯示。

https://ithelp.ithome.com.tw/upload/images/20261004/20184332xaE31C59mG.png

再按詳細顯示,A 的半糖少冰、B 的全糖正常冰應重新出現,訂單欄位沒有被刪除。

沒有 Provider 會怎樣

暫時移除最外層 Provider 的開頭與結尾標籤,保留整份 <main>。重新載入、加入一筆訂單,再按精簡顯示。

https://ithelp.ithome.com.tw/upload/images/20261004/2018433221TWH1Jjnw.png

按鈕的 aria-pressed 會改變,內容卻仍是詳細。因為 state 有更新,但讀取端找不到 Provider,只能使用 createContext 的預設值 "detailed"。


參考資料


上一篇
Day 19 : 規則集中,reducer 整理訂單邏輯
下一篇
Day 21 : 聚光燈輪到你,用 ref 操作 DOM
系列文
React 入門到實作與除錯|30 天哩ㄟ刻 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言