今天來學習如何使用 context 來解決 prop drilling 的問題。今天會學習的內容為:
prop drilling 就像生活中的水管系統一樣。想像一個大樓,裡面有好多層樓,每層樓都需要用水。但是水源卻在最頂層,所以每一層都需要一根水管來把水從頂樓運到下面。
這些水管就像是連接一樓、二樓、三樓的管道,如果一樓的人需要水,他得透過每一層的水管才能得到水。這就是 prop drilling,資料或訊息必須一層一層地傳遞,即使中間的樓層並不真的需要這些訊息。這樣的過程可能會讓程式碼變得複雜,就像在房子中需要多根水管一樣。而且如果樓層很多,水流(效能)可能會變慢,因為水要經過好多根水管。
(圖片出自 React 官方文件)
Context 在 React 當中是一個用於共享狀態和數據的機制,它允許資料傳遞到應用程式中的多個元件,而不需要將資料通過 props 一層層地傳遞。先來看一個例子,假設我們的應用程式是在不同的 section 中會有不同的 heading 大小。
初步的詳細的程式碼:https://codesandbox.io/s/ycy7tf?file=/Heading.js
按照之前的學習內容我們能透過 props 去進行這些操作:
<Section>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
<Heading level={4}>Sub-sub-heading</Heading>
</Section>
但這樣子可能造成的問題就是剛剛提到的 prop drilling。要解決這樣問題的話,理想應該是要像這樣直接從父元件Section
來控制,像這樣:
<Section level={4}>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
我們可以透過 Context 達成這樣的目的。讓我們來學習該如何操作:
在操作前先來看看會用到哪些檔案:
在LevelContext.js
檔案中我們需要建立希望共享的資料,並將資料 export。
import { createContext } from "react";
export const LevelContext = createContext(1);
這邊的 createContext()
的唯一參數為預設值,我們設定這邊設定1
是考量到最大的 level Heading。
創建好 context 後,我們像在可以開始使用了。但首先需要先引入useContext
。
需要先在管理 heading 的檔案中引入:
import { useContext } from "react";
import { LevelContext } from "./LevelContext.js";
原先不使用useContext
的操作方式也就是在Heading
元件 props 直接去讀取 level。
但這邊我們將Heading
元件中的level
屬性(prop)的讀取方式改為使用 React 的useContext
hook 從LevelContext
中獲取。
也就會是這樣子:
export default function Heading({ children }) {
const level = useContext(LevelContext);
// ...
}
我們這樣的操作也就是告訴了 React:Heading 元件想要讀取LevelContext
。
因爲沒有在Heading
中使用 level props 所以在 JSX 當中我們也可以改變成以下的寫法:
<Section level={4}>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
<Heading>Sub-sub-heading</Heading>
</Section>
在這個情境下,Heading
元件並不是直接從 LevelContext
中拿資料,而是透過 Section
父元件間接地獲取 level
資料。
這樣子就完成了第二步驟,已經把「接收」資料的路徑建置完成了。接下來要來把「提供」資料的路徑也建置出來。
目前 Section 元件在渲染的是它的 children,像這樣:
export default function Section({ children }) {
return <section className="section">{children}</section>;
}
但這樣子沒有辦法向子元件提供資料,所以需要調整成用 context provider 去包覆子元件(就是{children}
)。
import { LevelContext } from "./LevelContext.js";
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>{children}</LevelContext.Provider>
</section>
);
}
這邊的程式碼,我們操作了三件事:
(1) import LevelContext
(2) 以 context provider LevelContext.Provider
將{children}
包覆起來
(3) 將 value prop 設定為{level}
這樣子就能達到我們一開始想要的將資料透過父元件來傳遞了。