[React] contextAPI & reducer
這一次的範例是根據contextAPI的範例,將原本用useState儲存的name和theme改成使用 reducer 來儲存。
reducer 主要也是用來儲存狀態,但是會回傳物件,相較於useState可以儲存更為複雜的狀態邏輯。我們透過action來處理邏輯,裡面包含type(類型)和payload(負載)來去改變剛開始的狀態(Initial state),故我們會將setName與toggleTheme的邏輯寫入 reducer。
codesandbox: https://codesandbox.io/s/contextapi-reducer-o4glp7?file=/src/contexts/ThemeContext.jsx
basicReducer
在 basicReducer 內,接受原始狀態(state),和要進行處理的行動(action),我們分成兩種類型(type)的行動,分別是更新名字和變換主題,類型慣例使用全部大寫、使用_作為分隔,回傳的是新的物件,透過 es6 的語法(...operator)解構原始狀態,並以 payload 進行更新(TOGGLE_THEME 直接處理 theme 值變化)。
const basicReducer = (state, action) => {
const { type, payload } = action;
switch (type) {
case "UPDATE_NAME":
return { ...state, name: payload }; // payload 為該input的value
case "TOGGLE_THEME":
const { theme } = state;
return { ...state, theme: theme === "light" ? "dark" : "light" }; // 並非透過payload
default:
throw new Error(`Unhandled type ${type} in userReducer`);
}
};
BASIC_ACTION_TYPES
針對 basicReducer 的不同類型,我們可以儲存在一個額外的字典(BASIC_ACTION_TYPES),讓我們不會因為打錯字呼叫不到而跳出錯誤,參考以下
export const BASIC_ACTION_TYPES = {
UPDATE_NAME: "UPDATE_NAME",
TOGGLE_THEME: "TOGGLE_THEME",
};
修改 basicReducer
switch (type) {
case BASIC_ACTION_TYPES.UPDATE_NAME:
return { ...state, name: payload };
case BASIC_ACTION_TYPES.TOGGLE_THEME:
const { theme } = state;
return { ...state, theme: theme === "light" ? "dark" : "light" };
INITIAL_STATE
接著,我們建立 reducer 的原始狀態,為name和theme
const INITIAL_STATE = {
name: "",
theme: "light",
};
useReducer
useReducer 是 ReactHooks 的其中一種,帶入reducer和INITIAL_STATE,會回傳state(原始狀態)和dispatch(發送函式)
const [state, dispatch] = useReducer(basicReducer, INITIAL_STATE);
const { name, theme } = state; // 也可以在上一行解構賦值 const [{name, theme}, dispatch] = useReducer(basicReducer, INITIAL_STATE);
透過dispatch,我們帶入type和payload就會形成該類型的函式,在此範例中形成setName和toggleTheme
const setName = (text) => {
dispatch({
type: BASIC_ACTION_TYPES.UPDATE_NAME,
payload: text,
});
};
const toggleTheme = () =>
dispatch({
type: BASIC_ACTION_TYPES.TOGGLE_THEME,
});
最後再將name、setName、theme、toggleTheme透過<ThemeContext.provider>傳下去給在此背景中的各個元件使用。
const value = { name, setName, theme, toggleTheme };
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
如此一來,就完成了 contextAPI 和 reducer 的結合了!!!
結論
reducer 的概念跟Array.prototype.reduce很像,都是針對一個物件或是一個陣列進行邏輯變換,回傳一個新的值或物件等等。透過學習 reducer,我們同時間也往 redux 更邁進了一步,兩者有著許多相同的命名方式和使用邏輯,而 redux 有著十分豐富的生態系,我想先從基本著手,隨著專案的擴大與團隊的合作方式,再去選擇使用什麼樣的工具。