Skip to main content

[React] React with redux-thunk

redux-thunk是一個常用於 redux 的中介軟件(middleware),在處理非同步的呼叫時可以使用,使用後可以將資料的處理邏輯與 UX 呈現分開。

基本設定

首先要下載 npm 套件

npm install redux-thunk

yarn add redux-thunk

createStore裡面放入applyMiddleware

import thunk from "redux-thunk";

const store = createStore(rootReducer, applyMiddleware(thunk));

在這個網課中,有使用其他 middleware,所以寫法比較不一樣

store.js
const middleWares = [
process.env.NODE_ENV !== "production" && logger,
thunk,
].filter(Boolean);

const composedEnhancers = composeEnhancer(applyMiddleware(...middleWares));

export const store = createStore(
persistedReducer,
undefined,
composedEnhancers
);

非同步呼叫處理(aysnc)

接著,看到有進行非同步呼叫的shop.component.jsx,商店需要呼叫在 firebase 的資料,透過資料產生 UI,原本的邏輯都放在useEffect裡面的async函式中

src/routes/shop/shop.component.jsx
import { getCategoriesAndDocuments } from "../../utils/firebase/firebase.utils";

import { setCategories } from "../../store/categories/category.action";

useEffect(() => {
const getCategoriesMap = async () => {
const categoriesArray = await getCategoriesAndDocuments("categories");
dispatch(setCategories(categoriesArray));
};

getCategoriesMap();
}, []);

從上面可以看到,setCategories是一個設置categories的行動,而因為是非同步的關係,我們可以將這個 action 拆成三個階段,fetchStartfetchSuccessfetchFailure,並將資料處理放進category.reducer.js

src/store/categories/category.reducer.js
CATEGORIES_INITIAL_STATE = {
categories: [],
isLoading: false, // 新增的isLoading狀態可以在匯入時帶入不同的UI
error: null, // 發生error的時候帶入
};

export const categoriesReducer = (
const { type, payload } = action;

switch (type) {
case CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_START:
return {
...state,
isLoading: true, // 正在匯入的狀態
};
case CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_SUCCESS:
return { ...state, isLoading: false, categories: payload }; // payload為fetch到資料庫的資料
case CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_FAILED:
return { ...state, isLoading: false, error: payload }; // payload為傳入的error
default:
return state;
}

category.action.js裡面,也是依據三種情況,新增對應的 function,最後將他們綜合起來變為fetchCategoriesStartAsync的函式,結尾的 Async 通常為 redux 中使用 async 的時候會給的稱呼,這個函式處理了之前 async 函式的資料邏輯

src/store/categories/category.action.js
import { getCategoriesAndDocuments } from "../../utils/firebase/firebase.utils";

export const fetchCategoriesStart = () =>
createAction(CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_START);

export const fetchCategoriesSuccess = (categoriesArray) =>
createAction(
CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_SUCCESS,
categoriesArray
);

export const fetchCategoriesFailure = (error) =>
createAction(CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_FAILED, error);

// 綜合前面三個action,讓UI那邊只需要dispatch這個action
export const fetchCategoriesStartAsync = () => {
return async (dispatch) =>
// 這邊的dispatch突然出現,不是前面給予的參數,有些奇怪,而在影片教學的留言處有些人討論到,使用redux的dispatch呼叫的函式內,都可以使用到dispatch。
{
dispatch(fetchCategoriesStart());
try {
const categoriesArray = await getCategoriesAndDocuments("categories");
dispatch(fetchCategoriesSuccess(categoriesArray));
} catch (error) {
dispatch(fetchCategoriesFailure(error));
}
};
};

分派任務(dispatch action)

最後,回到原本的Shop元件,帶入我們剛剛寫好處理 async 呼叫資料的函式fetchCategoriesStartAsync,來取得categories

src/routes/shop/shop.component.jsx
import { fetchCategoriesStartAsync } from "../../store/categories/category.action";

useEffect(() => {
dispatch(fetchCategoriesStartAsync());
}, []);

結論

使用redux-thunk來處理aysnc的非同步呼叫函式,可以使得 UI 和資料處理的邏輯分開,較容易幫助除錯,而且原本的函式只有進行呼叫資料(setCategories),我們透過 redux-thunk 將資料分為

  1. fetch 開始
  2. fetch 成功
  3. fetch 失敗

三種狀態,有助於我們製作不同狀態之下的 UI。

參考資料