iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 12
5

前言

useContext 是能夠讓程式變得簡潔的利器,Component 不再需要經過一層一層的 Props 傳遞,便能直接使用在需要它的 Component。

少了 useContext 不會怎樣,但是多了它會很不一樣。


前置準備

  1. 文中的專案會以 Day10 的專案架構繼續講解,如果未跟到前一天的進度,可以從 GitHub 上 Clone 下來。
  2. 一顆擁有學習熱忱的心。

使用方法

使用情境

在學習 useContext 使用前,我們先打開 src/index.js,並將它更改為以下內容:

經過了前幾天的努力,大家應該都看得懂上方的程式碼內容,它 Render 結果會是一個待辦事項的清單:

清單的 Component 是 TodoList,假設現在我們要為該頁面增加待辦事項的筆數顯示,但又不要影響 TodoList 本身的功能(列出待辦事項),就得另外寫一個 Main Component 來當成主頁面,並且在 Main 裡使用 TodoList

如此一來,便可以在 Main 中加入其他要顯示的資訊,但這時候因為待辦事項的資料是存在於 TodoList 的 State 裡面,要在 Main 裡取得它就得將 State 的位置提升,並透過 Props 將待辦事項的資料給 TodoList,最後 ReactDom 便只需要 Render Main 頁面:

調整後,就能夠在不影響 TodoList 的狀況下添加其他需求,例如顯示待辦事項的筆數:

上方調用 Component 和 State 的技巧叫做 State 提升,藉由將 Stata 拉到最頂層來讓所有需要該 State 的 Component 共同使用,這樣便不會導致所有 Component 都維護著自己的 State,而當 State 發生改變時,又要找出所有使用的 Component 將它改成正確的值。

只要確定資料流的來源是單一的,那當該來源改變,所有使用它的 Component 都會重新 Render 為新值。

所以設計好 State 的位置非常重要。

那到這裡一定會有許多人疑惑,這篇不是該說明 useContext 嗎?怎麼講了一堆關於 State 的事情?

其實 useContext 正是要解決將 State 共同管理所造成的程式碼問題。

這裡的程式碼問題,並不是在執行時會有 Bug 或是出錯,而是讓程式碼不夠簡潔,怎麼說呢?

大家可以注意一下上方的 TodoList,原本待辦事項的 State 在它身上,但是因為 State 提升了,所以我們得透過 Props 將 State 送給 TodoList 接收,這麼看感覺還好,但是請試想,如果 MainTodoList 間又有一層 Component 呢?而那個 Component 根本就不需要用到 State,那就會形成一個尷尬的結果:

到這裡可能有點複雜,但請大家試著消化一下,原本在 Main 直接調用的 TodoList 變成一個頁面 TodoListPage 了,而該頁面裡也許有其他內容,但只有 TodoList 需要待辦事項的 State,那待辦事項的 State 就會先傳到完全不需要使用他的 TodoListPage,才能再送到真正需要他的 TodoList

聰明的讀者們可能會想說,那將待辦事項的 State 放到 TodoListPage 不就好了?但是在 Main 裡,還有另一個 CurrentTask 也需要用它呢......

雖然程式運行結果沒問題,但對 TodoListPage 來說,獲得 props.todoList 百分之百是多餘的程式碼,但你又不能將它刪去,因為 TodoList 需要它,那該怎麼辦?

打開傳送門吧!

useContext

useContext 提供了更方便做法,讓父 Component 能好好管理要提供的 Props,也讓子 Component 能更輕易地獲取 Props。

以上方的例子來說,我們可以在 Main 中使用 createContext 來創建要提供的 Props 的 Component,這很容易,只需要這麼做(useContext 會在下方使用):

import React, {
  useState, createContext, useContext
} from 'react';

const TodoListContext = createContext();

接下來,用剛建立的 Context 以 TodoListContext.Provider 的形式包覆 Main 所 Render 的內容,並將 Props 交給 TodoListContext.Provider 的 value,令人開心的是,到這個步驟,已經不需要再將 todoList 用 Props 提供給 TodoListPageCurrentTask 了,因此可以將它移除。

以上三個部分修改完後,Main 的內容會像:

清爽多了對吧!那鏡頭轉到 TodoListCurrentTask,現在少了 Props,子 Component 只能藉由 useContext 獲取 Context TodoListContext 管理的 value,當然 Props 什麼的也都可以直接拿掉:),最後 TodoList 會像:

CurrentTask 是:

然後可別遺漏 TodoListPage,我們得將在它之中那討厭的 Props 給刪去:

最後使用 npm run start 運行:

得到的結果會與使用 Props 相同,

但你的程式碼已經大不相同!

而因為少了一層一層傳遞的 Props,所以當初為 Component 設置的 propTypes 也就可以都拿掉了!萬歲!

本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)


結尾

useContext 解決了透過 Props 傳遞資料時常常會經過太多層,且讓不需要該資料的 Component 也都擁有的狀況,如果只是一層還沒關係,但是如果出現文章中舉出的例子,不妨可以試著感受一下 useContext 的魅力!

如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!


上一篇
Day10 | Props 太多, Component 就容易出錯, 就讓 Prop-Types 替你把關吧!
下一篇
Day12 | React 的快樂小夥伴 - Redux 資料管理篇
系列文
在 React 生態圈內打滾的一年 feat. TypeScript31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0

有個問題想請教,所以使用 useContext 後,包覆有使用該 state 的子元件的元件(該子元件的父層),就不會重新渲染了嗎?

神Q超人 iT邦研究生 5 級 ‧ 2019-09-23 19:58:47 檢舉

還是會哦!當 Provider 的 value 變化時,他的所有 children 還是會重新 rerender!因此我們要盡量在需要的時候使用,讓 Component 更乾淨,不會有多餘的 Code,要設置 Global 的 State 還是會推 Redux!

0
matuyou0301
iT邦新手 5 級 ‧ 2021-04-15 11:51:58

想請教一下

const {todolist} = props; 

const todolist = useContext(TodoListContext)

這兩個程式碼的差別在哪裡呢?
第一個todolist有括號,第二個則沒有,表示前者是陣列後者是物件嗎?
有點搞不太清楚這部分的差別:((

神Q超人 iT邦研究生 5 級 ‧ 2021-04-23 22:18:09 檢舉

其實第一個程式碼的意思是這樣子:

const todolost = props.todolist;

從 props 中拿出 todolist,只是方便一點的寫法而已 XD

XDDD,學到小技巧了! 感恩

0
連城
iT邦新手 4 級 ‧ 2021-08-13 18:27:36
const TodoListPage = () => (
  <div>
    <div>其他內容什麼的</div>
    <TodoList />
  </div>
);

會遇到錯誤

const TodoListPage = () => (
return(
  <div>
    <div>其他內容什麼的</div>
    <TodoList />
  </div>
 )
);

return 後解決

我要留言

立即登入留言