終究都要學 React 何不現在學呢? - React 進階 - useReducer - (15)

前言

接著我們要來認識一個很特別的東西,也就是 useReducer,而 useReducer 可以說是 useState 的進階版,因為 useReducer 可以讓我們在處理複雜的狀態時,可以更好的管理狀態,而不是像 useState 一樣,只能用一個變數來管理狀態。

useReducer

前言我們有提到 useReduceruseState 的進階版,簡單來講呢…useReducer 非常接近 Redux 寫法(一套用於資料狀態管理的函式庫,類似於 Vue 的 VuexPinia,不了解這些是什麼也沒關係,只是稍微提到而已)。

那麼接著就讓我們先看一下 useReducer 的語法吧!

1
const [state, dispatch] = useReducer(reducer, initialState, init);

看起來與 useState 很像,但是 useReducer 有三個參數,而 useState 只有兩個參數

1
const [state, dispatch] = useState(initialState);

可以明顯看出這兩個 Hook 是有差異的(廢話)。

接著讓我們看一下 useReducer 中的參數,也就是 reducerinitialStateinit 這三個參數,這邊先撇除 init 這個參數,因為這個參數是可選的,所以我們只需要著重於先看 reducerinitialState 這兩個參數。

這邊先題外話一下,其實在 React 官方文件有提到一句話

useState 的替代方案。接受一個 (state, action) => newState 的 reducer,然後回傳現在的 state 以及其配套的 dispatch 方法。

稍微有一點難懂,但其實重點就是在講 useReducer 就是另一個 useState (也就是前面講的變體),因此 useReducer 再回傳時其實也是回傳一個陣列,而這個陣列中的第一個元素就是 state,而第二個元素就是 dispatch

那麼實際上該怎麼使用 useReducer 呢?讓我們來看一下範例吧!

首先先宣告一個初始化的狀態 State

1
2
3
const initialState = {
count: 0,
};

請注意通常初始的狀態會是一個物件。

接下來是宣告一個叫做 reducer 的函式,請注意 reducer 會傳入兩個參數,分別是 stateaction

1
2
3
const reducer = (state, action) => {
// ...
}

而這個 reducer 函式裡面通常會使用到 switch 語法,並且是使用 action 參數當作判斷依據,有趣的是 action 會有一個屬性是 type,我們會使用該屬性當作判斷,所以接著裡面我們先寫一個 default 並寫回傳一個物件

1
2
3
4
5
6
const reducer = (state, action) => {
switch(action.type) {
default:
return { count: 0 }
}
}

接下來呢?讓我們回到 App 元件內,只需要在裡面使用 useReducer 並傳入 reducerinitialState 這兩個參數,就可以取得 statedispatch 這兩個變數囉~

1
2
3
const App = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
}

接下來畫面只需要這樣子撰寫就可以輕鬆渲染出 count 了!

1
2
3
4
5
return (
<div>
Count: { state.count }
</div>
);

我們可以看到畫面已經正常且正確的呈現,但實際上來講目前只是讓程式碼正常運作而已,但還沒真正開始操作 statedispatch,所以接下來我們就來操作一下 statedispatch

接下來為了能夠操作 useReducer 生出來的 statedispatch,因此畫面上增加兩個按鈕,然後我們會透過 dispatch 修改來 count

請注意! dispatch 會是傳入一個物件,而屬性會是 type(主要是搭配前面寫的 switch),因此 App 元件目前如下

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
const reducer = (state, action) => {
switch(action.type) {
default:
return { count: 0 }
}
}

const initialState = {
count: 0,
};


const App = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);

return (
<div>
Count: {state.count}
<br />
<button type="button" className="bg-blue-400 p-4 text-white hover:bg-blue-700" onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
<button type="button" className="bg-blue-400 p-4 text-white hover:bg-blue-700" onClick={() => dispatch({ type: "increment" })}>Increment</button>
</div>
);
}

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);

目前來講你點擊畫面是不會有任何反應的,因為前面 reducer 函式我們並沒有撰寫完成。

那麼我們透過 dispatch 傳入一個物件,並且裡面有一個 type,因此 reduceraction 就可以取出 type,此時就可以透過這個 type 去做一些操作,例如減少 count 或是增加 count 等行為,但這邊要注意回傳時是回傳一個物件,而這個物件裡面的 count 就是我們要做的一些操作與修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return {
count: state.count + 1
};
case 'decrement':
return {
count: state.count - 1
};
default:
return {
count: 0
}
}
}

目前為止,你的 useReducer 就已經可以正常操作了。

那麼 useReducer 跟 Vuex、Redux 有什麼不同呢?簡單來講其實 useReducer 是 React 本身所提供的一個 Hook,請注意是「本身」,代表著你不需要額外安裝就可以直接使用的一個功能,但是如果是 Vuex 跟 Redux 的話,則是框架之外所提供的狀態管理庫,因此你是必須額外安裝的。

基本上如果你的專案並沒有到非常複雜的話,其實是可以單純使用 useReducer 的,而 useReducer 本身就是一個簡單版的 Redux,但不代表可以完全取代 Redux,只是你可以透過 useReducer + useContext 來製作出簡易版的 Redux 來使用

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const CountContext = React.createContext();

const reducer = (state, action) => {
switch(action.type) {
case 'increment':
return {
count: state.count + 1
};
case 'decrement':
return {
count: state.count - 1
};
default:
return {
count: 0
}
}
}

const initialState = {
count: 0,
};

const Child = () => {
const [state, dispatch] = React.useContext(CountContext);
return (
<div>
Child Count: {state.count}
<br />
<button type="button" className="bg-blue-400 p-4 text-white hover:bg-blue-700" onClick={() => dispatch({ type: "decrement" })}>Decrement</button>
<button type="button" className="bg-blue-400 p-4 text-white hover:bg-blue-700" onClick={() => dispatch({ type: "increment" })}>Increment</button>
</div>
);
}

const App = () => {
const countReducer = React.useReducer(reducer, initialState);

return (
<div>
App Count: {countReducer[0].count}
<br />
<CountContext.Provider value={countReducer}>
<Child />
</CountContext.Provider>
</div>
);
}

const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App />);

雖然可以使用 useReducer + useContext 製作出簡易版的 Redux,但終究還是與真正的 Redux 有些差異,因此如果你的專案需要使用 Redux 的話,那麼你還是必須額外安裝 Redux 來使用的唷。

後記

本文將會同步更新到我的部落格