Lala Code

Lala 的前端大補帖,歡迎一起鑽研前端技術😊

0%

【DAY 22】useContext 跨組件溝通傳遞資料

cover

當專案越來越多 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 程式碼範例

props drilling

該怎麼解決這個問題?





認識 Context API

有的!Context API 可以解決 Props drilling 的問題!

Context API 可以「跨組件溝通傳遞資料」,讓組件可以省去組件層層傳遞的麻煩。

將 State 在最外層定義,Provider提供 Context value 給底下的組件使用,組件完全不用傳入 Props 就可以使用到 State

Context

useContext 是使用 Context API 的 Hook,幫助我們使用 Context API





useContext 使用方法

  • 創建 Context 檔案,輸出 Provider、context
  • 外層設定 state
  • 外層引入 Provider,將 state 傳入 Provider value
  • 組件使用 useContext 傳入 context,使用 state

跟著範例解說會比較清楚

useContext

現在我們有 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,並輸出 Providercontext 給內外層組件來使用



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 技能




🚀實體工作坊分享

玩轉 Web頁面的前端技術(HTML/CSS/JS) 一日體驗課

最近時賦學苑開了實體體驗課,即使你對程式碼沒有概念也能上手!Lala 會帶你一起做出一個個人品牌形象網站,帶你快速了解前端的開發流程,快跟我們一起玩轉 Web 吧!



🚀線上課程分享

線上課程可以加速學習的時間,省去了不少看文件的時間XD,以下是我推薦的一些課程
想學習更多關於前後端的線上課程,可以參考看看。

Hahow

Hahow 有各式各樣類型的課程,而且是無限次數觀看,對學生或上班族而言,不用擔心被時間綁住



六角學院

如果你是初學者,非常推薦六角學院哦!
剛開始轉職也是上了六角的課,非常的淺顯易懂,最重要的是,隨時還有線上的助教幫你解決問題!


Udemy

Udemy 裡的課程非常的多,品質普遍不錯,且價格都滿實惠的,CP值很高!
也是很多工程師推薦的線上課程網站。
❤️