React 的第九個 hook:useReducer

回來填坑。

簡述

之前碰到這個 hook 時就想說等學完 Redux 再來詳細解說吧,結果一眨眼兩、三個月就過去了 XD,總之這篇要來解釋一下 useReducer 的用途。

我覺得當這個 hook 很適合用在「當你不需要用到 Redux,但又想要全域 state」的時候。可以利用它來跟 Context 結合出一個替代方案。

所以看這篇前最好先知道一下 useContext 在幹嘛?因為等一下的範例會用到,建議可以先去看我之前寫的這兩篇:

useReducer 的好用之處?

「在學任何東西前,先問一下為什麼要用這個東西?」是句很經典的話,所以我們先來舉一個範例,一開始不使用 useReducer 接著再來解釋如果用了 useReducer 能帶來什麼好處?

假設我們目前要做一個可以切換「顏色主題」的網頁,底下的元件得根據目前的主題來切換樣式,所以除了需要 state 以外,我們還需要一個 Context 來避免「Props drilling」的問題。

所以呢,目前大概會有這樣的 code:

ThemeContext.jsx

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

// 建立一個元件包裝後再 export 出去
export function ThemeProvider ({ children }) {
const [theme, setTheme] = useState({
color: 'dodgerblue'
})

return (
<ThemeContext.Provider value={{theme, setTheme}}>
{children}
</ThemeContext.Provider>
)
}

這個是在 React-關於 useContext 更好的寫法 中提到的小技巧,忘記的話可以去複習一下。

App.js

1
2
3
4
5
6
7
8
9
10
11
import Nav from "./Nav";
import "./styles.css";
import { ThemeProvider } from "./ThemeContext";

export default function App() {
return (
<ThemeProvider>
<Nav />
</ThemeProvider>
);
}

接著,假設我們想透過導覽列上的按鈕來切換顏色,可能就會寫出這樣的東西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useContext } from "react";
import { ThemeContext } from "./ThemeContext";

export default function Nav() {
const { theme, setTheme } = useContext(ThemeContext);
const changeTheme = () =>
setTheme((prevTheme) => ({
...prevTheme,
color: "pink"
}));
return (
<div className="nav" style={{ background: theme.color }}>
<h1>Peanu</h1>
<button onClick={changeTheme}>Change Theme</button>
</div>
);

做到這邊有任何疑問的話可以先來這邊看一下範例,希望能幫助你理解一點。

總之呢,一個最簡單的 Context 就這樣完成了,但如果你仔細想一下會發現其實有可以改善的空間:

1. 邏輯全部塞在 function 裡

我知道這聽起來有點奇怪,畢竟在 function 放邏輯不是天經地義的事嗎?不然要放在哪裡?

別急,我的意思是指剛剛我們直接在 changeTheme 中用 setTheme 的這種方式其實應該有更好的寫法,畢竟當邏輯變得複雜時裡面的東西也會越來越多,可讀性就會沒那麼好。

這個時候如果我們可以透過 dispatch 的概念來改寫的話,不是會好很多嗎?像這樣:

1
2
3
4
5
6
function changeTheme () {
dispatch({
type: 'CHANGE_COLOR',
payload: 'pink'
})
}

2. state / function 零散各地

意思是當 ThemeContext 想建立其他的 state 時,state 就會越來越多個,像這樣:

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

// 建立一個元件包裝後再 export 出去
export function ThemeProvider ({ children }) {
const [theme, setTheme] = useState({
color: 'dodgerblue'
})
// 其他的 state
const [state2, setState2] = useState()
const [state3, setState3] = useState()

return (
<ThemeContext.Provider value={{
theme,
setTheme,
state2,
state3
}}>
{children}
</ThemeContext.Provider>
)
}

所以呢,這時候如果改用 useReducer 的話就可以改善這些問題。

加入 useReducer

這邊先介紹一下 useReducer 的用法,其實就跟在寫 redux 差不多,所以如果你對 redux 完全沒概念的話建議先去補一些相關知識再回來看,這邊不會解釋太深。

總之核心要素還是幾個東西:

  • reducer
  • action
  • dispatch

首先 useReducer 會接收兩個參數,分別為 initialStatereducer

initialState 就是初始的 state,reducer 則是用來處理每個 action 的 function。

所以強調一下:

  • reducer 就只是一個 function
  • reducer 就只是一個 function
  • reducer 就只是一個 function

接著 useReducer 會回傳兩個值(包在 Array 裡),分別為 statedispatch,前者就是 state 的值,後者則是用來發出 action 的一個 function。

其實這幾個東西都是 redux 的核心要素,所以再次建議你先去理解 redux 的概念後再回來看這篇,不然應該很難理解。

所以回到剛剛的例子,我們會把 useState 拿掉,改用 useReducer 來取代,變成這樣子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { createContext, useReducer } from "react";

export const ThemeContext = createContext();

// initialState
const initState = {
color: "dodgerblue"
};

// reducer
const themeReducer = (state, action) => {
switch (action.type) {
case "CHANGE_THEME":
return { ...state, color: action.payload };
default:
return state;
}
};

export function ThemeProvider({ children }) {
// 改用 useReducer 取代 useState
const [state, dispatch] = useReducer(themeReducer, initState);
// 用來改變 state 的 function
const changeTheme = (color) => {
dispatch({
type: "CHANGE_THEME",
payload: color
});
};

// 最後把 state 跟 changeTheme 當作 Provider 的值
return (
<ThemeContext.Provider value={{ state, changeTheme }}>
{children}
</ThemeContext.Provider>
);
}

最後完成的 範例 在這邊,有什麼疑問的話可以去玩玩看。

回顧一下剛剛提到的兩個問題,這邊改用 useReducer 後其實就都解決了:

1. 邏輯都寫在 function 裡

現在 function 要做的事情很單純,就是 dispatch 一個 action 給 reducer,僅此而已。

2. 需要很多不同的 state 來管理不同狀態

當改用 useReducer 後,我們可以把所有 state 放在一個 Object 管理就好,不需要再拆成一個一個。

所以以上就是 useReducer 的範例,希望之後能運用這個輕量型的 hook 來實現簡單的狀態管理。

JavaScript-處理錯誤的方法 React-關於 useContext 更好的寫法
Your browser is out-of-date!

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

×