iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Modern Web

JavaScript 進階修煉與一些 React ——離開初階工程師新手村的頭30天系列 第 12

離開 JS 初階工程師新手村的 Day 12|Context API:跨越地圖的傳送門

  • 分享至 

  • xImage
  •  

想像你正在建造一棟摩天大樓,每一層樓都需要電力供應。電力從一樓運輸到二樓,再從二樓傳到三樓,一層一層地傳遞下去。如果有一層樓不需要電力,還是得需要通過它才能到達更高的樓層。這就是 React 開發中著名的「Prop Drilling」問題。

Prop Drilling

在 React 的世界裡,資料流動遵循著嚴格的單向規則:從父組件流向子組件。當應用結構簡單時,這種方式清晰而直觀。但當組件層次變得複雜時,問題就出現了。

https://ithelp.ithome.com.tw/upload/images/20250923/20168365iL9lVLWgbZ.png

在這個例子中,LayoutHeader 組件本身並不需要使用者資料,它們只是資料傳遞的中介。當我們使用 context api 則可以解決這個問題,當注入此 context 時,所有底下的組件都可以拿到 Context 裡的狀態。

https://ithelp.ithome.com.tw/upload/images/20250923/20168365ggOFviXLRm.png

現在,LayoutHeader 不再需要承擔資料傳遞的責任,而 Navigation 可以直接從 Context 中獲取所需的資料。

原理

1. Context:資料的容器

Context 就像是一個 invisible 的資料倉庫,它存在於組件樹的某個層級,任何位於這個層級之下的組件都可以訪問其中的資料。

2. Provider:資料的廣播站

Provider 就像是一個廣播電台,它負責向所有「收聽者」廣播資料。當資料發生變化時,Provider 會通知所有訂閱了這個頻道的組件。

3. Consumer:資料的接收者

Consumer(現在主要透過 useContext hook)就像是收音機,它調頻到特定的 Context 頻道,接收並使用廣播的資料。

適用場景

全域設定類資料:這類資料的特點是「全域需要,變動不頻繁」。

https://ithelp.ithome.com.tw/upload/images/20250923/20168365SZiYD8hQ9A.png

使用者認證資訊:登入狀態、使用者權限等資料通常需要在多個組件中使用,但更新頻率相對較低。

https://ithelp.ithome.com.tw/upload/images/20250923/20168365qGEpY4ZrSC.png

多語言支援:語言設定通常在應用初始化時確定,之後很少變動,但需要在多個組件中使用。

通知提示視窗:當特定使用者行為成功或失敗,可以將提示視窗組件寫成 Context API 的形式,好處是組件化,而且無論如何本來就需要重新渲染,所以也不怕效能問題(接下來會討論到)。

全域重渲染

雖然 Context API 解決了 prop drilling 問題,但它也帶來了新的挑戰,如上面稍微提到的,Context 最大的效能陷阱在於全域重渲染。當 Context 的值發生任何變化時,所有使用該 Context 的組件都會重新渲染,無論它們是否真正使用了變化的資料。

https://ithelp.ithome.com.tw/upload/images/20250923/20168365TE3qaRY3dn.png

解法

1. Context 分割: 將大型的 Context 分割成多個更小、更專注的 Context
https://ithelp.ithome.com.tw/upload/images/20250923/201683653DRXYs51QQ.png

2. 記憶化優化: 使用 useMemo 來避免不必要的 value 物件重建
https://ithelp.ithome.com.tw/upload/images/20250923/20168365Egz0L7Wq89.png

最佳實踐

1. 自定義 Hook

永遠為你的 Context 創建對應的自定義 hook,這不僅提供了更好的開發體驗,在使用上更直觀,就像我們前幾天寫的 customize hook,還能進行錯誤檢查

https://ithelp.ithome.com.tw/upload/images/20250923/20168365IlGwWf1yRn.png

2. Provider 組合

使用組合模式來管理多個 Provider,避免過深的嵌套

https://ithelp.ithome.com.tw/upload/images/20250923/20168365zqw82rwzMA.png

3. 邊界設置

不要讓 Context 的範圍過於廣泛,將 Provider 放在真正需要該資料的組件樹的最高層級

困境

儘管 Context API 解決了 prop drilling 問題,但在大型應用中,它仍然存在一些根本性的挑戰。像是狀態的更新沒有辦法控制粒度 (granularity),當一個大型的 Component 裡某個屬性發生變化,所有使用該 Context 的組件都會重新 render,無論它有沒有直接使用到那個屬性。

另外,處理非同步操作時 (回顧此系列文討論的什麼是非同步) Context API 需要額外的狀態來追蹤狀態,像是 call 外部 API 成功或失敗與否,或是已經 call 了還沒回傳資料,需要避免再 call 一次,那整個工作使得 Context 變得太複雜。

在寫測試的時候也是因此而變得複雜,因為需要提供每個測試完整的 Provider。

這些種種的麻煩,現今有許多方便的工具,如 Redux 和 Zustand,雖不像 Context 是 React 原生的,但他們能實際針對部分更動的屬性進行更新以達到效能的提升,又或是提供更加完整的機制去追蹤非同步 API 的後續狀態。我們接下來幾天會繼續介紹~


上一篇
離開 JS 初階工程師新手村的 Day 11|useCallback / useMemo:進階優化魔法
系列文
JavaScript 進階修煉與一些 React ——離開初階工程師新手村的頭30天12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言