useContext
是能夠讓程式變得簡潔的利器,Component 不再需要經過一層一層的 Props 傳遞,便能直接使用在需要它的 Component。
少了 useContext
不會怎樣,但是多了它會很不一樣。
在學習 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 將它改成正確的值。
那到這裡一定會有許多人疑惑,這篇不是該說明 useContext
嗎?怎麼講了一堆關於 State 的事情?
useContext
正是要解決將 State 共同管理所造成的程式碼問題。這裡的程式碼問題,並不是在執行時會有 Bug 或是出錯,而是讓程式碼不夠簡潔,怎麼說呢?
大家可以注意一下上方的 TodoList
,原本待辦事項的 State 在它身上,但是因為 State 提升了,所以我們得透過 Props 將 State 送給 TodoList
接收,這麼看感覺還好,但是請試想,如果 Main
和 TodoList
間又有一層 Component 呢?而那個 Component 根本就不需要用到 State
,那就會形成一個尷尬的結果:
到這裡可能有點複雜,但請大家試著消化一下,原本在 Main
直接調用的 TodoList
變成一個頁面 TodoListPage
了,而該頁面裡也許有其他內容,但只有 TodoList
需要待辦事項的 State,那待辦事項的 State 就會先傳到完全不需要使用他的 TodoListPage
,才能再送到真正需要他的 TodoList
。
聰明的讀者們可能會想說,那將待辦事項的 State 放到 TodoListPage
不就好了?但是在 Main
裡,還有另一個 CurrentTask
也需要用它呢......
雖然程式運行結果沒問題,但對 TodoListPage
來說,獲得 props.todoList
百分之百是多餘的程式碼,但你又不能將它刪去,因為 TodoList
需要它,那該怎麼辦?
打開傳送門吧!
以上方的例子來說,我們可以在 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 提供給 TodoListPage
或 CurrentTask
了,因此可以將它移除。
以上三個部分修改完後,Main
的內容會像:
清爽多了對吧!那鏡頭轉到 TodoList
和 CurrentTask
,現在少了 Props,子 Component 只能藉由 useContext
獲取 Context TodoListContext
管理的 value,當然 Props 什麼的也都可以直接拿掉:),最後 TodoList
會像:
而 CurrentTask
是:
然後可別遺漏 TodoListPage
,我們得將在它之中那討厭的 Props
給刪去:
最後使用 npm run start
運行:
得到的結果會與使用 Props 相同,
而因為少了一層一層傳遞的 Props,所以當初為 Component 設置的 propTypes
也就可以都拿掉了!萬歲!
本文的範例程式碼會提供在 GitHub 上,歡迎各位參考:)
useContext
解決了透過 Props 傳遞資料時常常會經過太多層,且讓不需要該資料的 Component 也都擁有的狀況,如果只是一層還沒關係,但是如果出現文章中舉出的例子,不妨可以試著感受一下 useContext
的魅力!
如果文章中有任何問題,或是不理解的地方,都可以留言告訴我!謝謝大家!
有個問題想請教,所以使用 useContext 後,包覆有使用該 state 的子元件的元件(該子元件的父層),就不會重新渲染了嗎?
還是會哦!當 Provider 的 value 變化時,他的所有 children 還是會重新 rerender!因此我們要盡量在需要的時候使用,讓 Component 更乾淨,不會有多餘的 Code,要設置 Global 的 State 還是會推 Redux!
想請教一下
const {todolist} = props;
const todolist = useContext(TodoListContext)
這兩個程式碼的差別在哪裡呢?
第一個todolist有括號,第二個則沒有,表示前者是陣列後者是物件嗎?
有點搞不太清楚這部分的差別:((
其實第一個程式碼的意思是這樣子:
const todolost = props.todolist;
從 props 中拿出 todolist,只是方便一點的寫法而已 XD
XDDD,學到小技巧了! 感恩
const TodoListPage = () => (
<div>
<div>其他內容什麼的</div>
<TodoList />
</div>
);
會遇到錯誤
const TodoListPage = () => (
return(
<div>
<div>其他內容什麼的</div>
<TodoList />
</div>
)
);
return 後解決