Redux-saga 03 | saga 적용, 리덕스 개발자 도구(redux-devtools-extension) 활용

Kate Jung·2022년 1월 7일
0

middlewares & libraries

목록 보기
10/17
post-thumbnail
post-custom-banner

📌 saga 적용

🔹 비동기 처리

1. 액션 타입/액션 생성함수/saga 제작

흐름

  1. '비동기' 액션 타입 선언

    INCREASE_ASYNC, DECREASE_ASYNC

  2. 액션 생성 함수 제작 (해당 액션에 대한)

    • increaseAsync, decreaseAsync
      • () => undefined를 두 번째 파라미터로 넣음. → 마우스 클릭 이벤트가 payload 안에 들어가지 않도록
  3. saga(제너레이터 함수) 제작

코드 (modules/counter.js)

import { createAction, handleActions } from 'redux-actions';
**import {
  delay,
  put,
  takeEvery,
  takeLatest,
  select,
  throttle,
} from 'redux-saga/effects';**

// 액션 타입
const INCREASE = 'counter/INCREASE';
const DECREASE = 'counter/DECREASE';

**const INCREASE_ASYNC = 'counter/INCREASE_ASYNC';
const DECREASE_ASYNC = 'counter/DECREASE_ASYNC';**

// 액션 생성 함수
export const increase = createAction(INCREASE);
export const decrease = createAction(DECREASE);
// 마우스 클릭 이벤트가 payload 안에 들어가지 않도록
// () => undefined를 두 번째 파라미터로 넣음.
**export const increaseAsync = createAction(INCREASE_ASYNC, () => undefined);
export const decreaseAsync = createAction(DECREASE_ASYNC, () => undefined);**

**function* increaseSaga() {
  yield delay(1000); // 1초를 기다림
  yield put(increase()); // 특정 액션을 디스패치함.
}

function* decreaseSaga() {
  yield delay(1000); // 1초를 기다림.
  yield put(decrease()); // 특정 액션을 디스패치함.
}**

**export function* counterSaga() {
  // takeEvery: 들어오는 모든 액션에 대해 특정 작업 처리
  yield takeEvery(INCREASE_ASYNC, increaseSaga);

  // takeLatest: 가장 마지막 실행된 작업만 수행. (기존 진행 중이던 작업 있으면 취소 처리.)
  yield takeLatest(DECREASE_ASYNC, decreaseSaga);
}**

// 초기 값
const initialState = 0; // 상태는 꼭 객체일 필요 x. 숫자도 작동 가능

// 리듀서
const counter = handleActions(
  {
    [INCREASE]: (state) => state + 1,
    [DECREASE]: (state) => state - 1,
  },
  initialState,
);

export default counter;

2. 루트 사가 제작

  • 이유 : 다른 리듀서에서도 사가 만들어 등록 할 것

코드 (modules/index.js)

import { combineReducers } from 'redux';
import { all } from 'redux-saga/effects';
import counter, **{ counterSaga }** from './counter';
import sample from './sample';
import loading from './loading';

const rootReducer = combineReducers({
  counter,
  sample,
  loading,
});

**export function* rootSaga() {
  // all 함수: 여러 사가 합치는 역할
  yield all([counterSaga()]);
}**

export default rootReducer;

3. store에 redux-saga 미들웨어 적용

코드 (index.js)

// (나머지 생략)
import rootReducer, **{ rootSaga }** from './modules';
**import createSagaMiddleware from 'redux-saga';**

**const sagaMiddleware = createSagaMiddleware();**
const store = createStore(
  rootReducer,
  applyMiddleware(logger, ReduxThunk, **sagaMiddleware**),
);

**sagaMiddleware.run(rootSaga);**

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root'),
);

4. Container 컴포넌트 → App 컴포넌트에 렌더링

코드 (App.js)

import React from 'react';
**import CounterContainer from './containers/CounterContainer';**

const App = () => {
  return (
    <div>
      **<CounterContainer />**
    </div>
  );
};

export default App;

컨테이너 컴포넌트 내부 → 수정 없는 이유

해당 기능의 리덕스 모듈이 변경되었지만, 기존에 사용 중인 액션 생성 함수와 이름 동일함.

🔹 API 요청 상태 관리

◾ 참고

반복 코드 함수화 → 액션 타입, 디스패치, loading 처리, API 호출 (lib/createRequestSaga.js)


1. 액션 생성 함수/saga 제작

modules/sample.js

import { createAction, handleActions } from 'redux-actions';
**import { takeLatest } from 'redux-saga/effects';**
import * as api from '../lib/api';
**import createRequestSaga from '../lib/createRequestSaga';**

// 액션 타입 선언
const GET_POST = 'sample/GET_POST';
const GET_POST_SUCCESS = 'sample/GET_POST_SUCCESS';

const GET_USERS = 'sample/GET_USERS';
const GET_USERS_SUCCESS = 'sample/GET_USERS_SUCCESS';

**// 액션 생성 함수
export const getPost = createAction(GET_POST, (id) => id);
export const getUsers = createAction(GET_USERS);**

**// createRequestSaga에 분리:** 액션 타입, API 호출, loading 메소드
**const getPostSaga = createRequestSaga(GET_POST, api.getPost);
const getUsersSaga = createRequestSaga(GET_USERS, api.getUsers);**

**// Redux-saga**
**export function* sampleSaga() {
  yield takeLatest(GET_POST, getPostSaga);
  yield takeLatest(GET_USERS, getUsersSaga);
}**

// 초기 상태 선언
// 요청의 '로딩 중' 상태 관리 -> loading 객체
const initialState = {
  post: null,
  users: null,
};

// 리듀서
const sample = handleActions(
  {
    [GET_POST_SUCCESS]: (state, action) => ({
      ...state,
      post: action.payload,
    }),
    [GET_USERS_SUCCESS]: (state, action) => ({
      ...state,
      users: action.payload,
    }),
  },
  initialState,
);

export default sample;

2. 액션 타입, 디스패치, loading 처리, API 호출

lib/createRequestSaga.js

import { call, put } from 'redux-saga/effects';
import { startLoading, finishLoading } from '../modules/loading';

export default function createRequestSaga(type, request) {
	// 액션 타입 선언
  const SUCCESS = `${type}_SUCCESS`;
  const FAILURE = `${type}_FAILURE`;

  return function* (action) {
    yield put(startLoading(type)); // 로딩 시작
    // 파라미터로 action을 받아 오면 액션의 정보 조회 가능
    try {
      // call 사용 시, Promise 반환 함수 호출하고 기다릴 수 있음.
      const reponse = yield call(request, action.payload); // 의미: request(action.payload)
      yield put({
        type: SUCCESS,
        payload: reponse.data,
      });
    } catch (e) {
      // try/catch 문 사용하여 에러 잡기 가능
      yield put({
        type: FAILURE,
        payload: e,
        error: true,
      });
    }
    yield put(finishLoading(type)); // 로딩 끝
  };
}

참고

  • 요청에 필요한 값 전송하는 법

    • 경우 예시

      GET_POST 액션의 경우 → api요청 시 어떤 id로 조회할지 정해 주어야 함.

    1. 액션의 payload: 요청에 필요한 값

    2. 사가(액션 처리 하기 위한) 작성 시

      → API 호출 함수의 인수: payload값

  • API 호출 시

    • call 함수 사용 (사가 내부 직접 호출 x)

3. 루트 사가에 등록

  • sampleSaga를 루트 사가에 등록

코드 (modules/index.js)

import { combineReducers } from 'redux';
import { all } from 'redux-saga/effects';
import counter, { counterSaga } from './counter';
import sample, **{ sampleSaga }** from './sample';
import loading from './loading';

const rootReducer = combineReducers({
  counter,
  sample,
  loading,
});

export function* rootSaga() {
  // all 함수: 여러 사가 합치는 역할
  yield all([counterSaga(), **sampleSaga()**]);
}

export default rootReducer;

4. Container 컴포넌트 → App 컴포넌트에 렌더링

App.js

import React from 'react';
import SampleContainer from './containers/SampleContainer';

const App = () => {
  return (
    <div>
      <SampleContainer />
    </div>
  );
};

export default App;

📌 리덕스 개발자 도구(redux-devtools-extension) 활용

🔹 사용 이유

어떤 액션이 디스패치 되고 있는지 확인 → 편리

🔹 설치

$ yarn add redux-devtools-extension

🔹 사용법 (index.js)

  • composeWithDevTools→ 리덕스 미들웨어와 함께 사용 시

    applyMiddleware 부분 감싸기

  • 코드

    // (생략)
    **import { composeWithDevTools } from 'redux-devtools-extension';**
    
    const store = createStore(
      rootReducer,
      **composeWithDevTools(**applyMiddleware(logger, ReduxThunk, sagaMiddleware)**)**,
    );
    
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root'),
    );

참고

  • 벨로퍼트_리액트를 다루는 기술
profile
복습 목적 블로그 입니다.
post-custom-banner

0개의 댓글