[React] React with redux-saga
- redux-saga 筆記 by PJCHENder
- redux saga documentation 中文版
redux-saga 跟 redux-thunk 一樣,是用來控制 side effect 的 library,只是他具有的模式更為複雜。正因為它的複雜性,所以他提供對於非同步呼叫的作用(asynchronous side effects)的控制更為精確。
redux-saga 也是一種中介軟體(middleware),與一般在 redux 生態系的 middleware 不同在於,當 action 傳到 reducer 時,更新狀態後才會移到 saga,進行不同的處理,進到 saga 時,他們也可以觸發新的 action,再進到 reducers 更新狀態。
Sagas fire after the reducers have updated. actions fired by Saga's can trigger other sagas.
基本設置
npm 套件下載
npm add redux-saga
這次筆記記錄從 redux-thunk 轉為 redux-saga,透過網課的作品中進行修改。
參考程式碼: https://github.com/Stevetanus/crwn-clothing/commit/43a59a4636566f9337ca1712d43371423b9ddfb7
在store.js新增 redux-saga 的配置,跟 reducers 很像,會有一個 rootSaga,裡面放入所有的 saga。
首先,建立root-saga.js
import { all, call } from "redux-saga/effects";
export function* rootSaga() {} // function* 是建立一個generator函式,我們可以把它看作可以暫停的函式
回到store.js
import createSagaMiddleware from "redux-saga";
import { rootSaga } from "./root-saga";
const sagaMiddleware = createSagaMiddleware(); // 建立sagaMiddleware
const middleWares = [
process.env.NODE_ENV !== "production" && logger,
// thunk,
sagaMiddleware,
].filter(Boolean); // 帶入store的middleware
const composedEnhancers = composeEnhancer(applyMiddleware(...middleWares));
export const store = createStore(
persistedReducer,
undefined,
composedEnhancers
); // 放入middlewares
// after the store has been initiated with sagaMiddleware, we call sagaMiddleware to run
sagaMiddleware.run(rootSaga);
建立 Saga
在 saga 的基本設置結束後,我們更改fetchCategoriesAsync變成 Saga 的形式。
目標修改的函式如下
export const fetchCategoriesStartAsync = () => {
return async (dispatch) => {
dispatch(fetchCategoriesStart());
try {
const categoriesArray = await getCategoriesAndDocuments("categories");
dispatch(fetchCategoriesSuccess(categoriesArray));
} catch (error) {
dispatch(fetchCategoriesFailure(error));
}
};
};
先建立category.saga.js
import { takeLatest, all, call, put } from "redux-saga/effects"; // side effects generators
// all: run everything inside and only complete when all of it is done.
// takeLastest: If you hear a bunch of the same action, give me the latest one.(cancel the previous one, just restart with the latest one again)
// call: turn a function into effect; {CALL: {fn: function, args: [...params]}}
// put: put a dispatch action; {PUT: {type: 'action type'}}
import { getCategoriesAndDocuments } from "../../utils/firebase/firebase.utils";
import {
fetchCategoriesSuccess,
fetchCategoriesFailure,
} from "./category.action";
import { CATEGORIES_ACTION_TYPES } from "./category.types";
export function* fetchCategoriesAsync() {
try {
const categoriesArray = yield call(getCategoriesAndDocuments, "categories"); // call(method, param)
yield put(fetchCategoriesSuccess(categoriesArray));
} catch (error) {
yield put(fetchCategoriesFailure(error));
}
}
export function* onFetchCategories() {
yield takeLatest(
CATEGORIES_ACTION_TYPES.FETCH_CATEGORIES_START,
fetchCategoriesAsync
); // takeLatest(action, what you want to actually happen)
}
export function* categoriesSaga() {
yield all([call(onFetchCategories)]);
// further call is paused utill all finished
}
放回 root-saga
將剛剛寫好的categoriesSaga匯入root-saga.js
import { categoriesSaga } from "./categories/category.saga";
export function* rootSaga() {
yield all([call(categoriesSaga)]);
} // 一樣是透過all包起來,等待裡面的call(categoriesSaga)回傳JavaScript物件,yield到最後的物件
觸發 Saga
最後,回到shop.component.jsx,將useEffect內呼叫的函式改為fetchCategoriesStart,因為這是我們takeLastest監聽的函式,觸發後,才會跑fetchCategoriesAsync更新商品
import { fetchCategoriesStart } from "../../store/categories/category.action";
const Shop = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchCategoriesStart());
}, []);
...