Skip to main content

[React] contextAPI & reducer

這一次的範例是根據contextAPI的範例,將原本用useState儲存的nametheme改成使用 reducer 來儲存。

reducer 主要也是用來儲存狀態,但是會回傳物件,相較於useState可以儲存更為複雜的狀態邏輯。我們透過action來處理邏輯,裡面包含type(類型)和payload(負載)來去改變剛開始的狀態(Initial state),故我們會將setNametoggleTheme的邏輯寫入 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 的原始狀態,為nametheme

const INITIAL_STATE = {
name: "",
theme: "light",
};

useReducer

useReducer 是 ReactHooks 的其中一種,帶入reducerINITIAL_STATE,會回傳state(原始狀態)和dispatch(發送函式)

const [state, dispatch] = useReducer(basicReducer, INITIAL_STATE);
const { name, theme } = state; // 也可以在上一行解構賦值 const [{name, theme}, dispatch] = useReducer(basicReducer, INITIAL_STATE);

透過dispatch,我們帶入typepayload就會形成該類型的函式,在此範例中形成setNametoggleTheme

const setName = (text) => {
dispatch({
type: BASIC_ACTION_TYPES.UPDATE_NAME,
payload: text,
});
};

const toggleTheme = () =>
dispatch({
type: BASIC_ACTION_TYPES.TOGGLE_THEME,
});

最後再將namesetNamethemetoggleTheme透過<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 有著十分豐富的生態系,我想先從基本著手,隨著專案的擴大與團隊的合作方式,再去選擇使用什麼樣的工具。

參考資料