[React] Redux Saga

Haeseo Lee·2023년 4월 7일
0

React

목록 보기
12/16
post-thumbnail
post-custom-banner

Thunk 와의 차이

  • Redux Thunk
    • 함수를 dispatch 할 수 있게 해주는 미들웨어
  • Redux Saga
    • 액션을 모니터링 하고 있다가 특정 액션이 발생했을 때, 미리 정해둔 로직에 따라 특정 작업이 이루어지는 방식

Thunk의 단점

  • redux-thunk는 복잡한 비동기 로직의 디버깅과 추적이 어려움
  • action creator의 반환이 action object가 아님 (함수를 반환하므로)

Side Effect

  • e.g. 비동기 요청, 브라우저 캐싱, 로컬 스토리지 등
  • (javascript) 코드가 외부 세계에 영항을 주거나 받는 것

  • Effect는 middleware에 의해 수행되는 명령을 담고 있는 평범한 javascript 객체라고 생각하면 됨
  • Saga는 effect를 yield하고 Middleware는 effect를 처리

Blocking / Non-blocking

effect를 yield했다고 해서 middleware에서 처리가 끝날때까지 기다려주는 건 아님

blocking 예시

async function foo() {
	const res = await api.fetchA();
	console.log(res); // Response
}
function* fooSaga() {
	const res = yield call(api.fetchA);
	console.log(res); 
}

non-blocking 예시

function foo() {
	const promise = api.fetchA();
	console.log(promise); // Promise
}
function* fooSaga() {
	let task = yield fork(api.fetchA);
	console.log(task); 
}

Saga의 blocking / non-blocking 예시

function* saga() {
	yield take(ACTION);	// Blocking: will wait for the action
	yield call(ApiFn, ...args);	// Blocking: will wait for ApiFn
	yield call(otherSaga, ...args);	// Blocking: will wait for otherSaga to terminate

	yield put(action);	// Non-Blocking: will dispatch within internal schedular

	const task = yield fork(otherSaga, ...args);	// Non-Blocking: will not wait  for otherSaga
	yield cancel(task);	// Non-Blocking: will resume immediately
	// or
	yield join(task);	// Blocking: will wait for the task to terminate
}

Redux toolkit + Redux Saga

1) store 생성

import { configureStore } from "@reduxjs/toolkit";
import rootSaga from "./sagas/rootSaga";
import createSagaMiddleware from "@redux-saga/core";
import { createLogger } from 'redux-logger';

const logger = createLogger();

const rootReducer = combineReducers({
  product: productReducer,
  auth: authReducer,
});

const sagaMiddleware = createSagaMiddleware();
const store = **configureStore**({
  reducer: rootReducer,
  middleware: () => [sagaMiddleware, logger],
});
sagaMiddleware.run(rootSaga);

export default store;

2) slice 생성

  • slice: action creator와 reducer를 포함한 객체
// productSlice.js
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  data: [],
};

export const productSlice = createSlice({
  name: "product",
  initialState,
  reducers: {
    getProductList: (state, action) => {
      console.log("get productList");
    },
    searchProduct: (state, action) => {
      console.log("search productList");
    },
    setProductList: (state, action) => {
      console.log("set productList");
      state.data = [...action.payload];
    },
    setSearchResult: (state, action) => {
      console.log("set search result");
      state.data = [...action.payload];
    },
  },
});

export const { getProductList, searchProduct } = productSlice.actions;
export const productReducer = productSlice.reducer;
// authSlice.js
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
	auth: {},
	isLogin: false
}

export const authSlice = createSlice({
	name: 'auth',
	initialState,
	reducers: {
		login: (state, action) => {
			...
		},
		logout: (state, action) => {
			...
		),
	}
});

export const { login, logout } = authSlice.actions;
export const authReducer = authSlice.reducer;

3) saga 생성

// productSaga.js

import { takeEvery, takeLatest, put, take } from "redux-saga/effects";
import { createAction } from "@reduxjs/toolkit";
import axios from "axios";

const PRODUCT_LIST = createAction("product/getProductList");
const SEARCH_PRODUCT = createAction("product/searchProduct");
const SET_PRODUCT_LIST = createAction("product/setProductList");
const SET_SEARCH_RESULT = createAction("product/setSearchResult");

function* getProducts() {
  const response = yield axios.get("http://localhost:3001/products");
  const data = response.data;

  yield put({ type: SET_PRODUCT_LIST, payload: data });
}

function* searchProducts(data) {
  const response = yield axios.get(
    `http://localhost:3001/products?q=${data.payload}`
  );
  const result = response.data;
  yield put({ type: SET_SEARCH_RESULT, payload: result });
}

function* productSaga() {
  yield takeEvery(PRODUCT_LIST, getProducts);
  yield takeEvery(SEARCH_PRODUCT, searchProducts);
}

export default productSaga;
// authSaga.js

import { takeEvery, put } from "redux-saga/effects";
import { createAction } from "@reduxjs/toolkit";

const SIGNIN= createAction("auth/login");
const LOGOUT = createAction("auth/logout");

function* authSaga() {
  yield takeEvery(SIGNIN, );
  yield takeEvery(LOGOUT, );
}

export default authSaga;
// rootSaga.js

import { productSaga } from './productSaga';
import { authSaga } from './authSaga';

const rootSaga = function* () {
    yield all([ productSaga(), authSaga() ]);
};

export default rootSaga
profile
잡생각 많은 인간
post-custom-banner

0개의 댓글