React-關於 useContext 更好的寫法

總會有更好的寫法。

簡述

因為自己用到 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>
)
}

這樣子寫是沒什麼不對,但有幾個缺點:

  1. Context 的邏輯必須跟 App 寫在一起。想想看當 App 有其他邏輯的話,是不是有點混亂?
  2. 延續第一個問題,當有別的 Context 也放在 App 時,就會參雜更多邏輯跟各種 Provider

基於以上兩點,所以才會有接下來要介紹的寫法,讓你可以把「邏輯封裝在 Context 本身」,而不是混在一起大雜燴。

更好的寫法

這種作法就是當我們在建立 Context 時,順便建立一個「專屬 Context 的元件」,聽起來有點莫名,但你看 code 就應該懂了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// contexts/ThemeContext
import { createContext } from 'react'
// 存取 context 時還是得透過他,所以要 export 出去
export const ThemeContext = createContext()

// 建立一個元件包裝後再 export 出去
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 (
...
)
}

這樣當然沒問題,但就是每次都要 useContextThemeContext 有點小麻煩而已。

所以我們可以用 custom hook 來做包裝,像這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// hooks/useTheme.js
import { useContext } from "react"
import { ThemeContext } from "contexts/ThemeContext"

export const useTheme = () => {
const context = useContext(ThemeContext)

// 如果沒在正確的地方使用 Context
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 (
...
)
}
React 的第九個 hook:useReducer react-nprogress 懶人包
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×