當專案越來越多 Components 時,為了使用 props,會將 props 層層傳入,但也許這個 Component 根本沒用到這個 props,卻為了讓下層使用,而不得不傳入,這很容易造成混淆,形成了 Props drilling
舉個例子:
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useState } from "react"; import Header from "./Header"; import ProductList from "./ProductList";
const App = () => { const [orders, setOrders] = useState([]);
const addOrder = (order) => { setOrders([...orders, order]); };
return ( <div> <Header orders={orders} /> <ProductList addOrder={addOrder} /> </div> ); };
export default App;
|
Header 組件需要傳入 orders,ProductList 組件需要傳入 addOrder,所以 state 會寫在最外層的地方,並向下傳入 props
ProductList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import Product from "./Product";
const ProductList = (props) => { const { addOrder } = props;
const menu = [ { id: 0, name: "雞肉鍋" }, { id: 1, name: "豬肉鍋" }, { id: 2, name: "牛肉鍋" }, { id: 3, name: "海鮮鍋" }, { id: 4, name: "泡菜鍋" } ];
return ( <ul> {menu.map((item) => ( <Product key={item.id} {...item} addOrder={addOrder} /> ))} </ul> ); };
export default ProductList;
|
ProductList 未使用 addOrder,但因應下層 Product 需要,再繼續往下傳遞,等於 ProductList 只是個中繼站
Product.js
1 2 3 4 5 6 7 8 9 10 11 12
| const Product = (props) => { const { addOrder, id, name } = props;
return ( <li> <label>{name}</label> <button onClick={() => { addOrder(id); }}>+</button> </li> ); };
export default Product;
|
這時候的 Product 才接收到從 App 到 ProductList 傳遞下來的 Props
codesandbox 程式碼範例
該怎麼解決這個問題?
認識 Context API
有的!Context API 可以解決 Props drilling
的問題!
Context API 可以「跨組件溝通傳遞資料」,讓組件可以省去組件層層傳遞的麻煩。
將 State 在最外層定義,Provider
提供 Context value 給底下的組件使用,組件完全不用傳入 Props 就可以使用到 State
useContext
是使用 Context API 的 Hook,幫助我們使用 Context API
useContext 使用方法
- 創建 Context 檔案,輸出 Provider、context
- 外層設定 state
- 外層引入 Provider,將 state 傳入 Provider value
- 組件使用 useContext 傳入 context,使用 state
跟著範例解說會比較清楚
現在我們有 App、Header、ProductList、Product 組件
App
載入 Header、ProductList
Header
會依據點擊次數增加數量
ProductList
渲染 Product 組件,並將菜單傳入 Product
Product
品名 + 點擊按鈕
1. 創建 Context 檔案,輸出 Provider、context
context/index.js
1 2 3 4
| import { createContext } from "react"; const context = createContext(); export const { Provider } = context; export default context;
|
引入 createContext
,創建 context
,並輸出 Provider
、context
給內外層組件來使用
2. 外層設定 state
App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { useState } from "react"; import Header from "./Header"; import ProductList from "./ProductList";
const App = () => { const [orders, setOrders] = useState([]);
const addOrder = (order) => { setOrders([...orders, order]); };
return ( <div> <Header /> <ProductList /> </div> ); };
export default App;
|
載入組件,設定好要傳入的 state
3. 外層引入 Provider,將 state 傳入 Provider value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { useState } from "react"; import Header from "./Header"; import ProductList from "./ProductList"; import { Provider } from "./context";
const App = () => { const [orders, setOrders] = useState([]);
const addOrder = (order) => { setOrders([...orders, order]); };
const contextValue = { orders, addOrder };
return ( <div> <Provider value={contextValue}> <Header /> <ProductList /> </Provider> </div> ); };
export default App;
|
引入 Provider,將 state 包成 contextValue 傳入 Provider
的 value 屬性,如此一來,被 Provider 包住的組件都可以使用 contextValue 裡的 state
4. 組件使用 useContext 傳入 context,使用 state
components/ProductList.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from "react"; import Product from "./Product";
const ProductList = () => { const menu = [ { id: 0, name: "雞肉鍋" }, { id: 1, name: "豬肉鍋" }, { id: 2, name: "牛肉鍋" }, { id: 3, name: "海鮮鍋" }, { id: 4, name: "泡菜鍋" } ];
return ( <ul> {menu.map((item) => ( <Product key={item.id} {...item} /> ))} </ul> ); };
export default ProductList;
|
ProductList 不用傳入任何 context value
components/Product.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useContext } from "react"; import context from "./context";
const Product = ({ id, name }) => { const { addOrder } = useContext(context);
return ( <li> <label>{name}</label> <button onClick={() => { addOrder(id); }}>+</button> </li> ); };
export default Product;
|
Product 引入 useContext
,並將 context
傳入,這邊只需要按鈕的 function,所以只需要載入 context value 裡的 addOrder,就可以直接使用 addOrder
components/Header.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { useContext } from "react"; import context from "./context";
const Header = () => { const { orders } = useContext(context);
return ( <header> 購物車 (${orders.length}) </header> ); };
export default Header;
|
Header 也是同理,只需載入 context value 裡的 orders,就可以直接使用 orders
結語
Context API 幫助我們解決跨組件溝通的問題,在後面的章節會再講到,跟 Context API 有同樣功能的 「Redux Toolkit」,不過 Redux 使用上比 Context API 複雜多了,先喘口氣留到後面再講吧!
本文為 IT 鐵人賽系列文 你 React 了嗎? 30 天解鎖 React 技能
🚀實體工作坊分享
最近時賦學苑開了實體體驗課,即使你對程式碼沒有概念也能上手!Lala 會帶你一起做出一個個人品牌形象網站,帶你快速了解前端的開發流程,快跟我們一起玩轉 Web 吧!
🚀線上課程分享
線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。
Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住
如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!
Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。