總會有更好的寫法。
簡述
因為自己用到 useContext 的頻率不高,所以在用的時候都用比較正統的方式來撰寫。
到了現在才發現原本有一種更好的寫法,可以把邏輯交給 Context 自己來處理,而不是寫在根元素上。總之這是個蠻不錯的練習,所以想記錄一下。
原本的寫法
假設有一個可以自定義主題的 Context,那最正統的寫法會像這樣:
| 12
 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 就應該懂了:
| 12
 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 的部分就可以改寫成這樣:
| 12
 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 的時候,我們多半會這樣寫:
| 12
 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 來做包裝,像這樣:
| 12
 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
 }
 
 | 
接著就可以直接拿來用了:
| 12
 3
 4
 5
 6
 7
 8
 
 | import { useTheme } from "hooks/useTheme"
 function Component () {
 const { theme } = useTheme()
 return (
 ...
 )
 }
 
 |