【React hook】深入淺出 10 分鐘理解 useContext

閱讀時間約 13 分鐘
如何深入淺出理解 useContext
我們知道,在 React 元件中,是無法直接水平跨元件傳遞 Props。若要,會需要先傳至父層元件,再傳遞下來。舉個例子:我們在建立電商網站時,可以想像在產品頁面,會包裹在瀏覽頁面、首頁等元件中。要讓如購物車的元件,接收到產品頁面傳遞的 Props,就需要隔著傳遞好幾層。這會造成兩個問題:
  • 部分元件並不會使用 Props,卻仍然需要傳遞。
  • 部分元件在重構後,可能導致 Props 忘記移除或難以辨識。
透過 Context,相較於使用 Redux,Context 可以提供的輕量級共享數據功能,並水平傳遞數據給其他元件使用。而 useContext,則提供了更容易理解的方式,來達到共享數據的目的。

什麼是 useContext?

useContext 是 React 提供的一組 hook,讓我們可以在元件(Component)中訪問其他組件的內容(Context)。
而使用 useContext 帶來的優勢,是我們可以將數據,直接傳遞給組件樹當中的深層組件,而不用一層一層使用 Props 傳遞。

如何使用 Context 和 useContext?

在使用 Context 時,可以想像我們有一個負責提供資料的容器 Provider,以及負責接收資料的 Consumer。只要被容器 Provider 包裹到的元件,該元件與以下的元件,全部都能透過 Comsumer 或 useContext,接收到所需的資料。
以下就示範兩種 Context 的使用方式,同時也能從中發現,為何 useContext 會被發明出來:
  1. Provider + Consumer 的 Context 模式
  2. Provider + useContext 的模式

1.Provider + Consumer 的 Context 模式

在這種模式下,我們首先需要創建一個 Context 對象,然後使用 Context.Provider 在組件樹中,將我們想要提供數據的範圍,使用元件包裹起來。
通常我們會包裹在頂層,換句話說就是在 App.js 的元件裡;但當然也可以僅包裹在特定的組件子數。並在該串元件樹之中,使用 Consumer 來「消費」之中的 Context。使用流程如下:
  1. 建立資料夾與命名文件
  2. 創建 Provider
  3. 建立 Provider 元件
  4. 包裹欲接收元件樹
  5. 讓元件接收 value 資料

1.建立資料夾與命名文件

在元件資料夾裡,新增一個 store 或 state 的資料夾(通常慣用 store)。接著在 state 資料夾裡,新增想要管理的 props 檔案,使用 xxxx-context.js 等方式命名。同常命名要以小寫開頭,因為 context 並非元件。

2.創建 Provider

import react,並使用 const XxxxContext = React.createContext() 來創建 Props。其中,XxxxContext 本身並不是元件,而是一個「乘載元件的容器」。

3.建立 Provider 元件

此處為了讓 App.js 維持清晰,我們特意將 Provider 抽離出來,獨立使用 XxxxProvider 的元件,讓整個程式碼變得簡潔。當然,也可以直接將這段寫在 App.js 裡。
這樣寫也帶來了另一個優勢,所有處理 Context 的邏輯,全部都可以寫在這個 Provider 裡面。接著,使用export default XxxxProvider; 讓其他元件可以存取。
將我們希望接收範圍的元件,使用 <AuthContext.Provider>...</AuthContext.Provider> 包裹。接著,將希望能夠傳出的值,傳入屬性 value 裡。此處使用物件或字串皆可。
此處未來會作為傳入一個更新的物件,因為 React 主要就是偵測傳入的 value 是否有變動,並據此重新渲染所有使用 Context 的元件。

4.包裹欲接收元件樹

接著在我們希望存取的物件,例如 Loggin 狀態希望可以讓所有人都存取,則在 App.js 引入。透過前面抽離的 Provider,我們可以簡單將整層的 <Layout /> 元件包裹起來。接著從 Layout 開始到往下所有的元件,全部都可以讀取 Context 提供的資料。

5.讓元件接收 value 資料

接著,在欲使用 Props 的元件,在 return 中使用 <AuthContext.Comsumer> 包裹起來,並在之中的元件,使用 {} 包裹,並將元件 HTML 包裹在匿名函式中。參數通常使用 ctx,就可以透過 ctx.totalAmount 來取值。
這個方法也是之前非 hook 的寫法,目前一樣可行。因為包裹 Consumer 的關係,容易導致 return 內容過於冗長。因此通常建議使用 useContext。

B. Provider + useContext 的模式

前面創建 Provider 的方式,和原先的 Provider + Consumer 的方式一模一樣,僅是後面 Consumer 的模式,被 useContext 所取代。以下一樣完整展示整個流程:
  1. 建立資料夾與命名文件
  2. 創建 Provider
  3. 建立 Provider 元件
  4. 包裹欲接收元件樹
  5. 讓元件接收 value 資料

1.建立資料夾與命名文件

在元件資料夾裡,一樣新增一個 store 或 state 的資料夾(通常慣用 store)。

2.創建 Provider

import react,並使用 const XxxxContext = React.createContext() 來創建 Props。其中,XxxxContext 本身並不是元件,而是一個「乘載元件的容器」。
小技巧:為了讓 IDE 更方便自動補齊,可以在 xxx-context.js 裡,在想要使用的屬性,放入空數值或空的匿名函式。

3.建立 Provider 元件

此處為了讓 App.js 維持清晰,我們特意將 Provider 抽離出來,獨立使用 XxxxProvider 的元件,讓整個程式碼變得簡潔。當然,也可以直接將這段寫在 App.js 裡。
這樣寫也帶來了另一個優勢,所有處理 Context 的邏輯,全部都可以寫在這個 Provider 裡面。接著,使用export default XxxxProvider; 讓其他元件可以存取。
以下是建立流程:
  1. 在 auth-context.js 中,創建希望能傳遞的屬性或方法,為了讓 IDE 自動補齊。
  2. 創建新元件 AuthContextProvider,並將 children 傳入並返回 <CartContext.Provider>{children}</CartContext.Provider>。
  3. 在 App.js 中,將 render 改成 <AuthContextProvider><Layout /></AuthContextProvider>,使所有的物件全部都吃的到 Context。
  4. 將所有的 useEffect、useState、Handler 等,都放入 AuthContextProvider 元件中處理。
將預計傳入的值,放在 <AuthContext.Provider value={{ totalAmount: totalAmount }}>。注意,此處前面的 totalAmount 是 key,後面的值是傳入的變數。

4.包裹欲接收元件樹

接著在我們希望存取的物件,例如 TotalAmount 狀態希望可以讓所有人都存取,則在 App.js 引入。透過前面抽離的 Provider,我們可以簡單將整層的 <Layout /> 元件包裹起來。接著從 Layout 開始到往下所有的元件,全部都可以讀取 Context 提供的資料。

5.讓元件接收 value 資料

這一步,也是 useContext 最大的不同,以及所帶來的優勢–––程式碼簡潔。但並非所有的傳值,都需要使用 useContext 來修改。如果在傳值後子元件就能夠直接使用,則一樣使用 props 傳值就很夠用,不需特別改成 useContext 傳值。
在所有預計引用 props 的元件,使用 cont cartCtx = useContext(CartContext); 來獲得所有的 Props 和 Handler。因為各個模組間的變數是獨立的,所以所有的 Context 都可以取名為 cartCtx。
整個解析過程如下:
  1. 引入 context:import CartContext from "../../../store/cart-context";
  2. 傳入 useContext:const cartCtx = useContext(CartContext);
  3. 賦值解構物件:const { items } = cartCtx;
  4. 使用 value:const totalNumberOfItems = items.reduce(…);
其中,使用 reduce 的原因,主要是為了加總目前全部的 item amount,所以使用 reduce。而預設參數 0 的原因,則是為了避免在沒有資料時,reduce 會報錯 undefined。

使用 useContext 的必知必會

A. useContext 不適用於高頻轉換

因為 Context 也有另一個潛在的缺點:useContext 並不是設計給高頻轉換的功能,因此如果需要大量更新的狀態, useContext 並非好的選擇。若真的要使用跨元件的 props 溝通高頻狀態變更(每秒就更新一次或數次等),可以使用 Redux 來規劃。

B. useContext 的雙重包裹

在某些情況下,我們可能需要在應用程序中的多個層次上使用相同的 Context。在這種情況下,useContext 可以被雙重包裹,並且可以由多個 Context 提供者進行傳遞。範例如下:

C. useContext 僅能接收 Context

注意,useContext 只能接收 Context 對象作為參數,而不能接收其他 Hook 或函式。

使用 useContext 時,多考量不同面向

當我們使用到 useContext 時,通常就會想要拿來取代多層的 props 傳遞;但 useContext 其實也有一個潛在門檻,便是當我們使用 useContext 處理共用元件時,會導致共用元件僅能處理特定 Case。
舉個例子,如果我們有一個 Modal 總是會在輸入錯誤時,跳出錯誤訊息;那 Input 輸入錯誤和 Submit 輸入錯誤的行為,預設會輸出不同的內容。這時如果使用 useContext 管理狀態,就容易導致狀態處理變得單一。
但若在單一個 Context 中放入多個狀態,又容易導致意外的 bug,例如錯誤引入狀態等。因此建議若是在處理 UI 元件如 Modal 時,還是使用 props 傳值處理。

在解決程式問題前,先思考如何解決

很多時候,我自己一看到問題,就會想要跳下去解決,開始找合適的解方;但每一次總是延伸很多問題。後來,我才慢慢養成了「先釐清和規劃問題」,再實際寫 Code 的習慣。我把具體的流程寫在這裡(延伸閱讀:****自學程式語言前,你真正該注意的問題解決技巧)。**
除此之外,學習新技術的過程,一定也會經歷不少挑戰。我自己也是學了好幾年,才意識到「原來學習充滿錯誤,才是常態」的心態。因此也寫了一篇「如何以恰當的心態學習任何事物」,避免自己重蹈覆轍。
希望這一篇 useContext 的文章,能幫助到你。有任何的問題,都歡迎你隨時留言交流!

延伸閱讀:

資料引用:

為什麼會看到廣告
此處作為整理前端(Frontend)和相關的 HTML、CSS、JavaScript、React 等前端觀念與技巧,全部都會收錄在這個專題之中。同時也會將相關的技術與反思記錄在此,歡迎各位讀者互相交流。
留言0
查看全部
發表第一個留言支持創作者!
接續上一篇 (上篇)為什麼目標設定又失敗?實戰目標管理 4 階段,讓你達成夢想目標!,本篇來說明四個階段的「階段三」和「階段四」: 目標設定 階段三:調整執行心態 目標設定 階段四:目標設定覆盤 這個階段,也是超過 70% 的機率,是採取正確的心態,與持續調整步伐。讓該專案意外順利堅持,並最終完成目
目標設定 階段一:確認當前目標 目標設定 階段二:設定可行目標 目標設定 階段三:調整執行心態 目標設定 階段四:目標設定覆盤
時間管理 技巧一:縱覽全局、明訂目標 時間管理 技巧二:善用零碎時間 時間管理 技巧三:根據目標校正心態
在轉職寫程式、自學程式語言的過程中,最害怕的莫過於遇到無從下手的問題。透過實際案例分享,讓零基礎從零到一的程式新手,也能快速學會如何解決複雜問題。
在學習寫程式或新技能時,過程一定非常痛苦,更別說能夠利用通勤時間來學習。我在四個月的在職學習期間,累積近 40 個小時的課程時間,超過 300 則的學習筆記。就來看看我是怎麼做到的吧!
在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。本文就以實際的例子來說明各自的差異,以及在未來使用時需要注意哪一些細節。
接續上一篇 (上篇)為什麼目標設定又失敗?實戰目標管理 4 階段,讓你達成夢想目標!,本篇來說明四個階段的「階段三」和「階段四」: 目標設定 階段三:調整執行心態 目標設定 階段四:目標設定覆盤 這個階段,也是超過 70% 的機率,是採取正確的心態,與持續調整步伐。讓該專案意外順利堅持,並最終完成目
目標設定 階段一:確認當前目標 目標設定 階段二:設定可行目標 目標設定 階段三:調整執行心態 目標設定 階段四:目標設定覆盤
時間管理 技巧一:縱覽全局、明訂目標 時間管理 技巧二:善用零碎時間 時間管理 技巧三:根據目標校正心態
在轉職寫程式、自學程式語言的過程中,最害怕的莫過於遇到無從下手的問題。透過實際案例分享,讓零基礎從零到一的程式新手,也能快速學會如何解決複雜問題。
在學習寫程式或新技能時,過程一定非常痛苦,更別說能夠利用通勤時間來學習。我在四個月的在職學習期間,累積近 40 個小時的課程時間,超過 300 則的學習筆記。就來看看我是怎麼做到的吧!
在傳統開發的過程中,很容易會搞混一般的 this 和箭頭函式(arrow function)中的 lexcial "this" 兩者的差異。本文就以實際的例子來說明各自的差異,以及在未來使用時需要注意哪一些細節。
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
此篇主要紀錄PWA開發過程心得,內容為PWA有什麼特色,為何React可以使PWA成為接近App的操作體驗,以概念敘述與簡單開發流程紀錄來介紹PWA。 什麼是漸進式網路應用程式(PWA) PWA擁有傳統網頁和移動應用的優點,使網頁應用程序可以像應用程序一樣運行,提供更加優越的用戶體驗
React Hooks 是 React 16.8 中引入的一組新的 API,允許你在函數組件中使用狀態和其他 React 特性,而不需要寫類組件。 狀態管理: useState 鉤子允許在函數組件中添加狀態。 副作用管理: useEffect 鉤子允許處理副作用,如數據獲取、訂閱和手動 DO
06. React Forms: Dynamically Add New Input Fields On Click || Learn React Through Mini Projects
07. React useContext Hook (User Context Example) || Learn React Through Mini Projects
Thumbnail
前言 嗨,各位懷舊遊戲愛好者!今天要跟大家分享一個有趣的主題:如何利用React和Pixi.js這兩大神兵利器,重塑我們那個年代的經典紅白機打磚塊遊戲! 先跟大家簡單科普一下,React是一個超級火爆的前端框架,能讓我們輕鬆創建可重用的UI組件,組件間的狀態管理也相當方便。。。
Thumbnail
想要知道如何用最新技術來製作一個App嗎? 跟著JayLin用React | Redux Tool Kit | TypeScript | TailwildCSS 來製作一個Drawing App
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
此篇主要紀錄PWA開發過程心得,內容為PWA有什麼特色,為何React可以使PWA成為接近App的操作體驗,以概念敘述與簡單開發流程紀錄來介紹PWA。 什麼是漸進式網路應用程式(PWA) PWA擁有傳統網頁和移動應用的優點,使網頁應用程序可以像應用程序一樣運行,提供更加優越的用戶體驗
React Hooks 是 React 16.8 中引入的一組新的 API,允許你在函數組件中使用狀態和其他 React 特性,而不需要寫類組件。 狀態管理: useState 鉤子允許在函數組件中添加狀態。 副作用管理: useEffect 鉤子允許處理副作用,如數據獲取、訂閱和手動 DO
06. React Forms: Dynamically Add New Input Fields On Click || Learn React Through Mini Projects
07. React useContext Hook (User Context Example) || Learn React Through Mini Projects
Thumbnail
前言 嗨,各位懷舊遊戲愛好者!今天要跟大家分享一個有趣的主題:如何利用React和Pixi.js這兩大神兵利器,重塑我們那個年代的經典紅白機打磚塊遊戲! 先跟大家簡單科普一下,React是一個超級火爆的前端框架,能讓我們輕鬆創建可重用的UI組件,組件間的狀態管理也相當方便。。。
Thumbnail
想要知道如何用最新技術來製作一個App嗎? 跟著JayLin用React | Redux Tool Kit | TypeScript | TailwildCSS 來製作一個Drawing App