公告貼了,群組也說了,就是有人不看
用了 reducer 整理訂單更新,把新增、刪除、改杯數和改規格的規則放在一起,事件函式送出 action,reducer 計算下一份訂單,清單與檢視再透過 props 收到資料。
今天加入一個顯示設定:詳細時顯示規格與金額,精簡時只顯示名稱和杯數。
觀察一下資料傳遞路徑,從清單拆出顯示單筆內容的內層元件,這份設定會:
`DrinkOrder` -> `OrderList` -> `OrderItemDetails`
但是 OrderList 只是排列清單,不需要使用這份設定,這種資料逐層經過只負責轉交的元件,就會遇到常說的 prop drilling。
Context 讓外層元件提供資料,由下方需要的元件直接讀取,中間層不必另外接收和轉交這份設定。
共享的值在這個專案中,我們使用 "detailed" 和 "compact" 兩種,先認識三個角色:
| 名稱 | 在這次練習裡做什麼 |
|---|---|
createContext |
建立提供端與讀取端共同使用的 Context |
| Provider | 用 value 提供這個範圍的顯示設定 |
useContext |
讓元件讀取並訂閱這份設定 |
Provider 就是資料提供者。元件讀取時,會往元件樹上方找最近的同一個 Context Provider;找不到,才使用建立 Context 時指定的預設值。
顯示模式放在 DrinkOrder 的 useState,按鈕呼叫 setDisplayMode 更新,Context 負責將目前的值傳下去,讀取端再決定要顯示哪些文字。
以現在的飲料訂單資料為例,會這樣安排:
| 資料 | 傳遞方式 |
|---|---|
每筆訂單 item、目前檢視的 order |
使用 props,明確指定元件顯示哪一筆 |
| 加杯、刪除、套用規格等回呼 | 沿用 props,交給外層元件處理 |
共同的顯示模式 displayMode |
使用 Context,讓需要的內層元件讀取 |
| 未套用的規格草稿 | 保留在 OrderEditor 的局部 state |
當多個較深的元件需要同一份設定時,Context 比較能減少轉交的工作,或是透過 children 組合元件,縮短需要傳遞資料的層數。
匯入 createContext 與 useContext,保留原本的 Hook 與型別,在所有 import 後、元件函式外建立:
type DisplayMode = "detailed" | "compact";
const DisplayModeContext = createContext<DisplayMode>("detailed");
DisplayMode 限制設定只能是詳細或精簡,"detailed" 是找不到 Provider 時的預設值,Context 放在元件外建立,讓提供端與讀取端使用同一個物件。
在 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:
初始合計是 2 筆、3 杯、85 元。
按精簡顯示,清單與檢視會省略規格和單筆金額,保留名稱、杯數與按鈕,清單上方的總額仍是 85 元,草稿編輯區也照常顯示。

再按詳細顯示,A 的半糖少冰、B 的全糖正常冰應重新出現,訂單欄位沒有被刪除。
暫時移除最外層 Provider 的開頭與結尾標籤,保留整份 <main>。重新載入、加入一筆訂單,再按精簡顯示。

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