接著我們要來認識一個很特別的東西,也就是 useReducer
,而 useReducer
可以說是 useState
的進階版,因為 useReducer
可以讓我們在處理複雜的狀態時,可以更好的管理狀態,而不是像 useState
一樣,只能用一個變數來管理狀態。
前言我們有提到 useReducer
是 useState
的進階版,簡單來講呢...useReducer
非常接近 Redux 寫法(一套用於資料狀態管理的函式庫,類似於 Vue 的 Vuex、Pinia,不了解這些是什麼也沒關係,只是稍微提到而已)。
那麼接著就讓我們先看一下 useReducer
的語法吧!
const [state, dispatch] = useReducer(reducer, initialState, init);
看起來與 useState
很像,但是 useReducer
有三個參數,而 useState
只有兩個參數
const [state, dispatch] = useState(initialState);
可以明顯看出這兩個 Hook 是有差異的(廢話)。
接著讓我們看一下 useReducer
中的參數,也就是 reducer
、initialState
、init
這三個參數,這邊先撇除 init
這個參數,因為這個參數是可選的,所以我們只需要著重於先看 reducer
跟 initialState
這兩個參數。
這邊先題外話一下,其實在 React 官方文件有提到一句話
useState 的替代方案。接受一個 (state, action) => newState 的 reducer,然後回傳現在的 state 以及其配套的 dispatch 方法。
稍微有一點難懂,但其實重點就是在講 useReducer
就是另一個 useState
(也就是前面講的變體),因此 useReducer
再回傳時其實也是回傳一個陣列,而這個陣列中的第一個元素就是 state
,而第二個元素就是 dispatch
。
那麼實際上該怎麼使用 useReducer
呢?讓我們來看一下範例吧!
首先先宣告一個初始化的狀態 State
const initialState = {
count: 0,
};
請注意通常初始的狀態會是一個物件。
接下來是宣告一個叫做 reducer
的函式,請注意 reducer
會傳入兩個參數,分別是 state
與 action
const reducer = (state, action) => {
// ...
}
而這個 reducer
函式裡面通常會使用到 switch
語法,並且是使用 action
參數當作判斷依據,有趣的是 action
會有一個屬性是 type
,我們會使用該屬性當作判斷,所以接著裡面我們先寫一個 default
並寫回傳一個物件
const reducer = (state, action) => {
switch(action.type) {
default:
return { count: 0 }
}
}
接下來呢?讓我們回到 App
元件內,只需要在裡面使用 useReducer
並傳入 reducer
跟 initialState
這兩個參數,就可以取得 state
跟 dispatch
這兩個變數囉~
const App = () => {
const [state, dispatch] = React.useReducer(reducer, initialState);
}
接下來畫面只需要這樣子撰寫就可以輕鬆渲染出 count
了!
return (
<div>
Count: { state.count }
</div>
);
我們可以看到畫面已經正常且正確的呈現,但實際上來講目前只是讓程式碼正常運作而已,但還沒真正開始操作 state
跟 dispatch
,所以接下來我們就來操作一下 state
跟 dispatch
。
接下來為了能夠操作 useReducer
生出來的 state
跟 dispatch
,因此畫面上增加兩個按鈕,然後我們會透過 dispatch
修改來 count
。
請注意! dispatch
會是傳入一個物件,而屬性會是 type
(主要是搭配前面寫的 switch
),因此 App
元件目前如下
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
,因此 reducer
的 action
就可以取出 type
,此時就可以透過這個 type
去做一些操作,例如減少 count
或是增加 count
等行為,但這邊要注意回傳時是回傳一個物件,而這個物件裡面的 count
就是我們要做的一些操作與修改
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 來使用
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 來使用的唷。
本文將會同步更新到我的部落格