Front-end/React

[React] Redux Saga: ๐ŸŒ€ ๋น„๋™๊ธฐ ๋งˆ๋ฒ•์‚ฌ๋กœ ๋ณ€์‹ ํ•˜๊ธฐ! ๐Ÿง™‍โ™‚๏ธ

xeunnie 2025. 1. 8. 01:00
728x90
๋ฐ˜์‘ํ˜•

Redux Saga ํ†บ์•„๋ณด๊ธฐ! ๐Ÿง™‍โ™‚๏ธ

Redux Saga๋Š” Redux์—์„œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊น”๋”ํ•˜๊ณ  ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๋ฏธ๋“ค์›จ์–ด์˜ˆ์š”. ์ด๋ฆ„๋ถ€ํ„ฐ ๋ญ”๊ฐ€ ์Šค์ผ€์ผ์ด ์ปค ๋ณด์ด์ง€ ์•Š๋‚˜์š”? “์‚ฌ๊ฐ€(Saga)“๋Š” ์—ฐ๋Œ€๊ธฐ๋ผ๋Š” ๋œป์ธ๋ฐ, ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ด์•ผ๊ธฐ์ฒ˜๋Ÿผ ์ž˜ ์งœ์—ฌ์ง„ ํ๋ฆ„์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ฒ ๋‹ค! ๋ผ๋Š” ํฌ๋ถ€๋ฅผ ๋‹ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ค๋Š˜์€ Redux Saga๊ฐ€ ์™œ ํ•„์š”ํ•œ์ง€, ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์–ด๋–ค ๋งค๋ ฅ์ด ์žˆ๋Š”์ง€ ๋‹ค๋ค„๋ณผ๊ฒŒ์š”. ๐ŸŒŸ


โ›ฒ๏ธ Redux Saga๋Š” ์™œ ํ•„์š”ํ•œ๊ฐ€์š”?

์šฐ๋ฆฌ๊ฐ€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์ด๋Ÿฐ ์ƒํ™ฉ๊ณผ ๋งˆ์ฃผํ•˜๊ฒŒ ๋ผ์š”:

  1. ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด API ํ˜ธ์ถœ์„ ํ•ด์•ผ ํ•œ๋‹ค.
  2. ํ˜ธ์ถœ ์ค‘์— ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ , ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด ๋ฉ”์‹œ์ง€๋„ ๋„์›Œ์•ผ ํ•œ๋‹ค.
  3. ์š”์ฒญ์ด ๋๋‚˜๋ฉด ์„ฑ๊ณต ์—ฌ๋ถ€์— ๋”ฐ๋ผ ๋‹ค๋ฅธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด Redux Thunk ๊ฐ™์€ ๋„๊ตฌ๋„ ์žˆ์ง€๋งŒ, ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ํ๋ฆ„์„ ๋‹ค๋ฃฐ ๋• ์ฝ”๋“œ๊ฐ€ ์ ์  ๊ธธ์–ด์ง€๊ณ  ์—‰์ผœ๋ฒ„๋ฆด ์ˆ˜ ์žˆ์–ด์š”.

Redux Saga๋Š” ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜์™€ ์ดํŽ™ํŠธ๋ฅผ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ๋กœ์ง์„ ๊น”๋”ํ•˜๊ฒŒ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค˜์š”.


๐Ÿ“ Redux Saga์˜ ํ•ต์‹ฌ ๊ฐœ๋…

์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜๋ž€?

Redux Saga์˜ ํ•ต์‹ฌ์€ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.

์ œ๋„ˆ๋ ˆ์ดํ„ฐ๋Š” function*์œผ๋กœ ์„ ์–ธํ•˜๋ฉฐ, yield ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ํ•จ์ˆ˜ ์‹คํ–‰์„ ์ž ์‹œ ๋ฉˆ์ถœ ์ˆ˜ ์žˆ์–ด์š”.

function* generatorExample() {
  console.log("Start");
  yield "Pause 1";
  console.log("Middle");
  yield "Pause 2";
  console.log("End");
}

 

์ œ๋„ˆ๋ ˆ์ดํ„ฐ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ํ•จ์ˆ˜๊ฐ€ ํ•œ ๋‹จ๊ณ„์”ฉ ๋ฉˆ์ถ”๋ฉฐ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค. Redux Saga๋Š” ์ด “๋ฉˆ์ถ”๊ณ  ์‹คํ–‰”์˜ ํŠน์„ฑ์„ ํ™œ์šฉํ•ด ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


๐Ÿ›๏ธ Redux Saga ์„ค์น˜ํ•˜๊ธฐ

๋จผ์ € ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ด์•ผ๊ฒ ์ฃ ?

npm install redux-saga

๐ŸŽก Redux Saga ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

Redux Saga๋ฅผ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๋ ค๋ฉด ์‚ฌ๊ฐ€(Saga)๋ฅผ ์ž‘์„ฑํ•˜๊ณ , ๋ฏธ๋“ค์›จ์–ด๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•ด์š”.

1๏ธโƒฃ ์‚ฌ๊ฐ€ ์ž‘์„ฑํ•˜๊ธฐ

import { takeEvery, put, call } from "redux-saga/effects";

// 1. ๋น„๋™๊ธฐ ์ž‘์—… ํ•จ์ˆ˜
function* fetchDataSaga() {
  try {
    const response = yield call(fetch, "https://jsonplaceholder.typicode.com/posts");
    const data = yield response.json();
    yield put({ type: "FETCH_SUCCESS", payload: data });
  } catch (error) {
    yield put({ type: "FETCH_FAILURE", error });
  }
}

// 2. ๊ฐ์‹œ์ž ์‚ฌ๊ฐ€
function* watchFetchData() {
  yield takeEvery("FETCH_REQUEST", fetchDataSaga);
}

export default watchFetchData;

 

 

2๏ธโƒฃ ์‚ฌ๊ฐ€ ๋ฏธ๋“ค์›จ์–ด ๋“ฑ๋กํ•˜๊ธฐ

์ด์ œ Redux ์Šคํ† ์–ด์— Redux Saga ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";
import rootReducer from "./reducers";
import rootSaga from "./sagas";

const sagaMiddleware = createSagaMiddleware();

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

// ์‚ฌ๊ฐ€ ์‹คํ–‰
sagaMiddleware.run(rootSaga);

export default store;

๐ŸŒŸ Redux Saga์˜ ์ฃผ์š” ์ดํŽ™ํŠธ

Redux Saga๋Š” ๋น„๋™๊ธฐ ์ž‘์—…์„ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ์ดํŽ™ํŠธ(Effects)๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด ์ดํŽ™ํŠธ๋Š” ์‚ฌ๊ฐ€ ๋‚ด๋ถ€์—์„œ ์‚ฌ์šฉํ•˜๋Š” ํŠน๋ณ„ํ•œ ๋ช…๋ น์–ด ๊ฐ™์€ ๊ฑฐ์˜ˆ์š”!

 

๐ŸŒ€ call(fn, ...args)

๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. fetch๋‚˜ axios ๊ฐ™์€ ๋น„๋™๊ธฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ์— ์‚ฌ์šฉ๋ผ์š”.

const data = yield call(api.fetchUser, userId);

 

๐ŸŒ€ put(action)

Redux ์Šคํ† ์–ด์— ์•ก์…˜์„ ๋””์ŠคํŒจ์น˜ํ•ฉ๋‹ˆ๋‹ค.

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

 

๐ŸŒ€ takeEvery(actionType, saga)

ํŠน์ • ์•ก์…˜์ด ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ์ง€์ •ํ•œ ์‚ฌ๊ฐ€๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

yield takeEvery("FETCH_REQUEST", fetchDataSaga);

 

๐ŸŒ€ takeLatest(actionType, saga)

๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฐœ์ƒํ•œ ์•ก์…˜๋งŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด์ „ ์š”์ฒญ์€ ๋ฌด์‹œ๋ผ์š”.

yield takeLatest("FETCH_REQUEST", fetchDataSaga);

 

๐ŸŒ€ select(selector)

ํ˜„์žฌ ์ƒํƒœ๋ฅผ ์„ ํƒ(select)ํ•ฉ๋‹ˆ๋‹ค.

const user = yield select((state) => state.user);

๐Ÿ‘ฉ๐Ÿป‍๐Ÿ’ป ์‹ค์ „ ์˜ˆ์ œ: ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ

์•ก์…˜ ์ƒ์„ฑ์ž

export const fetchUserRequest = () => ({ type: "FETCH_USER_REQUEST" });

 

๋ฆฌ๋“€์„œ

const initialState = {
  loading: false,
  user: null,
  error: null,
};

function userReducer(state = initialState, action) {
  switch (action.type) {
    case "FETCH_USER_REQUEST":
      return { ...state, loading: true };
    case "FETCH_USER_SUCCESS":
      return { ...state, loading: false, user: action.payload };
    case "FETCH_USER_FAILURE":
      return { ...state, loading: false, error: action.error };
    default:
      return state;
  }
}

export default userReducer;

 

์‚ฌ๊ฐ€

import { takeEvery, call, put } from "redux-saga/effects";

function* fetchUserSaga() {
  try {
    const response = yield call(fetch, "https://jsonplaceholder.typicode.com/users/1");
    const user = yield response.json();
    yield put({ type: "FETCH_USER_SUCCESS", payload: user });
  } catch (error) {
    yield put({ type: "FETCH_USER_FAILURE", error: error.message });
  }
}

function* watchFetchUser() {
  yield takeEvery("FETCH_USER_REQUEST", fetchUserSaga);
}

export default watchFetchUser;

๐ŸฅŠ Redux Saga vs Redux Thunk

Redux Saga์™€ Thunk๋Š” ๋‘˜ ๋‹ค ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ ‘๊ทผ ๋ฐฉ์‹์ด ๋‹ค๋ฆ…๋‹ˆ๋‹ค.

ํŠน์ง• Redux Thunk Redux Saga
๊ธฐ๋ฐ˜ ํ•จ์ˆ˜ ๊ธฐ๋ฐ˜ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜
๋ณต์žกํ•œ ์ž‘์—… ์ฒ˜๋ฆฌ ์–ด๋ ค์›€ ํšจ์œจ์ 
ํ•™์Šต ๊ณก์„  ๋‚ฎ์Œ ๋†’์Œ
์œ ์Šค ์ผ€์ด์Šค ๊ฐ„๋‹จํ•œ ๋น„๋™๊ธฐ ๋กœ์ง ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ํ๋ฆ„

๐Ÿ”– ๊ฒฐ๋ก 

 

Redux Saga๋Š” ๋ณต์žกํ•œ ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

๊ทธ ํž˜์€ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ํ•จ์ˆ˜์™€ ์ดํŽ™ํŠธ์˜ ์กฐํ•ฉ์—์„œ ๋‚˜์˜ค์ฃ . ์ฒ˜์Œ์—๋Š” ์•ฝ๊ฐ„ ํ—ท๊ฐˆ๋ฆด ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์ต์ˆ™ํ•ด์ง€๋ฉด ์ •๋ง ๊น”๋”ํ•˜๊ณ  ์œ ์—ฐํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

๐ŸŒท์ „์„ค์˜ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜์–ด๋ด…์‹œ๋‹น!๐ŸŒท

728x90
๋ฐ˜์‘ํ˜•