總會有更好的寫法。
簡述
因為自己用到 useContext 的頻率不高,所以在用的時候都用比較正統的方式來撰寫。
到了現在才發現原本有一種更好的寫法,可以把邏輯交給 Context 自己來處理,而不是寫在根元素上。總之這是個蠻不錯的練習,所以想記錄一下。
原本的寫法
假設有一個可以自定義主題的 Context,那最正統的寫法會像這樣:
1 2 3 4 5 6 7 8 9 10 11
| import { ThemeContext } from "../contexts/ThemeContext"
function App () { const [theme, setTheme] = useState('blue') return ( <ThemeContext.Provider value={theme, setTheme}> <Component /> </ThemeContext.Provider> ) }
|
這樣子寫是沒什麼不對,但有幾個缺點:
Context 的邏輯必須跟 App 寫在一起。想想看當 App 有其他邏輯的話,是不是有點混亂?
- 延續第一個問題,當有別的
Context 也放在 App 時,就會參雜更多邏輯跟各種 Provider。
基於以上兩點,所以才會有接下來要介紹的寫法,讓你可以把「邏輯封裝在 Context 本身」,而不是混在一起大雜燴。
更好的寫法
這種作法就是當我們在建立 Context 時,順便建立一個「專屬 Context 的元件」,聽起來有點莫名,但你看 code 就應該懂了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createContext } from 'react'
export const ThemeContext = createContext()
export function ThemeProvider ({ children }) { const [theme, setTheme] = useState({ color: 'blue' }) return ( <ThemeContext.Provider value={{theme, setTheme}}> {children} </ThemeContext.Provider> ) }
|
接著 App 的部分就可以改寫成這樣:
1 2 3 4 5 6 7 8 9
| import { ThemeProvider } from "../contexts/ThemeContext"
function App () { return ( <ThemeProvider> <Component /> </ThemeProvider> ) }
|
應該一眼就能看出這跟原本的差別了吧?現在這個明顯乾淨很多,而且日後要維護的話就只要到對應的 Context 去修改就好,簡直一舉兩得。
不過說穿了我們只是巧妙的運用了 children 來包裝而已,其實跟原本的寫法做的事情一模一樣。
Bonus:使用 custom hook 來減少 boilerplate
在存取 Context 的時候,我們多半會這樣寫:
1 2 3 4 5 6 7 8 9
| import { useContext } from "react" import { ThemeContext } from "contexts/ThemeContext"
function Component () { const { theme } = useContext(ThemeContext) return ( ... ) }
|
這樣當然沒問題,但就是每次都要 useContext 跟 ThemeContext 有點小麻煩而已。
所以我們可以用 custom hook 來做包裝,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { useContext } from "react" import { ThemeContext } from "contexts/ThemeContext"
export const useTheme = () => { const context = useContext(ThemeContext) if (context === undefined) { throw new Error('useTheme() must be used inside a ThemeProvider') } return context }
|
接著就可以直接拿來用了:
1 2 3 4 5 6 7 8
| import { useTheme } from "hooks/useTheme"
function Component () { const { theme } = useTheme() return ( ... ) }
|