[TIL] 230118

먼지·2023년 1월 18일
0

TIL

목록 보기
51/57
post-thumbnail

Redux Saga

기초 비동기 카운터 구현

미들웨어 설치

$ yarn add redux-saga
# or
$ npm i redux-saga

saga 함수 작성

effects 란 리덕스 사가 미들웨어가 수행하도록 작업을 명령하는 것

  • yield 를 해줘야 함
  • delay : 몇 milliseconds 동안 기다려라
  • put : 특정 액션을 디스패치해라
  • takeEvery : 첫 번째 인자의 액션이 디스패치 될 때마다, 두 번째 인자(함수?)의 코드를 실행
  • takeLatest : 가장 마지막으로 들어온 것만 처리.

사가 함수를 만들고 나서는 특정 액션에 대하여 특정 사가 함수가 호출되도록 설정할 수 있음.

// modules/counter.js

import { delay, put, takeEvery } from 'redux-saga/effects';

const INCREASE = 'INCREASE';
const DECREASE = 'DECREASE';
const INCREASE_ASYNC = 'INCREASE_ASYNC';
const DECREASE_ASYNC = 'DECREASE_ASYNC';

const increase = () => ({ type: INCREASE });
const decrease = () => ({ type: DECREASE });

// thunk함수로처리했던걸 -> 순수객체를반환하는함수로바꿈
export const increaseAsync = () => ({ type: INCREASE_ASYNC });
export const decreaseAsync = () => ({ type: DECREASE_ASYNC });

// saga
// - generator function으로 작성
function* increaseSaga() {
  yield delay(1000); // 1초기달려라. 특정작업을명령하기위해선effect를yield!
  yield put(increase()); // put=dispatch. increase를호출해서액션객체를만들고그액션을디스패치하라고리덕스사가미들웨어에게명령
}

function* decreaseSaga() {
  yield delay(1000); 
  yield put(decrease());
}

// 액션을 모니터링해서 만약에 INCREASE_ASYNC라는 액션이 디스패치 됐을 때
// increaseSaga 안에 코드를 실행하는 작업 => takeEvery effect
export function* counterSaga() {
  // 추후 INCREASE_ASYNC가 디스패치 되면, increaseSaga 코드를 실행
  // => 모든 INCREASE_ASYNC 액션들에 대하여 이 액션이 디스패치 되면 increaseSaga를 호출하겠다.
  yield takeEvery(INCREASE_ASYNC, increaseSaga); 
  // 가장 마지막으로 들어온 DECREASE_ASYNC만 처리. 즉, 기존에 decreaseSaga에서
  // delay로 1초를 기다리는 도중 새로운 게 들어온다면 기존 거는 무시하고 가장 마지막으로 들어온 거만 처리
  yield takeLatest(DECREASE_ASYNC, decreaseSaga);
}

counterSaga 이 saga에서는 어떤 액션이 디스패치 됐을 때 어떤 작업을 수행할지 정의하고 있음. 이건 여러 가지 리듀서를 가지고 루트 리듀서를 만드는 것처럼 루트 사가를 만들기 위해 내보내줘야 함.

// modules/index.js

import { all } from 'redux-saga/effects';

...

export function* rootSaga() {
  yield all([counterSaga(),  other saga...]);
}

redux store에 saga middleware 적용

// index.js

import rootReducer, { rootSaga } from './modules';
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';

...

const sagaMiddleware = createSagaMiddleware();

// thunk와 순서 상관 없음
const store = createStore(
  rootReducer,
  composeWithDevTools(
    applyMiddleware(
      sagaMiddleware,
      // thunk.withExtraArgument({ history: customHistory }),
      logger
    )
  )
);

// sagaMiddleware 안에 있는 run 함수 호출해야 함. 호출할 때 root saga 등록
sagaMiddleware.run(rootSaga); // 호출 x. 함수 자체를 파라미터로 전달

=> +1 버튼을 누르면 INCREASE_ASYNC 액션이 디스패치 되는데 이때 순수객체가 디스패치됨. 그리고 1초 뒤에 INCREASE 액션이 디스패치됨.

takeLatest, takeEvery, takeLeading

1초 안에 여러 액션이 디스패치 될 때. 서로 작동 방식이 어떻게 다를까?

+1 버튼을 연속해서 3번 누르면 1초 후에 1 2 3 올라감. 반면 -1을 계속 누르면 맨 마지막에 디스패치된 액션에 대해서만 사가의 코드가 호출돼서 작업을 멈출 때까지 숫자가 내려가지 않음.

export function* counterSaga() {
  yield takeEvery(INCREASE_ASYNC, increaseSaga); 
  yield takeLatest(DECREASE_ASYNC, decreaseSaga);
  // => 가장 마지막으로 들어온 것만 처리하겠다
}

takeLeading을 하면 가장 먼저 들어오는 것만 처리하고 사전 실행 중인 것이 있으면 무시함.
-1을 계속 누르면 가장 먼저 들어온 거 처리하고 끝날 때까지 다른 거 받지 않다가 끝나고 나면 다른 거를 받는 식을 작동함. +1은 누르는 대로 액션이 디스패치됨

export function* counterSaga() {
  yield takeEvery(INCREASE_ASYNC, increaseSaga); 
  yield takeLeading(DECREASE_ASYNC, decreaseSaga);
  // => takeLeading의 경우, 가장 먼저 들어온 DECREASE_ASYNC 액션에 대하여 decreaseSaga 안에 있는 함수들을 호출하겠다.
}

Promise 다루기

Redux Thunk 로 다룬 getPost promise function 구조.
비동기 작업 중 특정 파라미터가 필요하면 thunk creator function의 파라미터로 원하는 값을 가져올 수 있음. 그리고 내부에서 요청을 처리한 후 성공과 실패 여부에 따라 다른 액션을 디스패치함. 그리고 thunk 함수 자체인 함수 타입을 dispatch 해서 비동기 작업을 시작함.

export const getPost = id => async dispatch => {
  dispatch({ type: GET_POST, meta: id }); // 요청 시작
  try {
    const post = await postsAPI.getPostById(id); // API 호출
    dispatch({ type: GET_POST_SUCCESS, payload: post, ...}); // 성공
  } catch (e) {
    dispatch({ type: GET_POST_ERROR, ... }); // 실패
  }
}

Redux Saga 에서는 작동 방식이 다름. 순수 액션(type 값을 가지고 있는 객체) 생성 함수를 만듦. 이런 액션이 디스패치 됐을 때는, 액션을 모니터링해서 saga가 실행되도록 할 수 있음. 그럼 saga에서는 이 액션에 대한 정보를 파라미터로 가져올 수 있음. 액션 값은 결국 액션 생성 함수를 통해 만든 액션!
=> redux saga를 사용하면 순수 액션을 디스패치하는 형태로 비동기 작업을 할 수 있음

export const getPost = id => ({ type: GET_POST, payload: id, meta: id });

function* getPostSaga(action) {
  const id = action.payload;
  try {
    // effect : redux saga middleware에게 특정 작업을 하라고 명령하는 것
    // call : 해당 함수를 id parameter를 사용해서 호출해라
    // 함수가 만약 프로미스를 반환한다면 그 프로미스가 끝날 때까지 기다렸다가 post라는 값의 결과물을 담음
    const post = yield call(postAPI.getPostById, id);
    // 그리고 이에 따라, 새로운 액션을 put effect를 사용해 dispatch 함
    yield put({
      type: GET_POST_SUCCESS,
      payload: post,
      meta: id
    });
  } catch (e) {
    // error가 발생하면 try catch로 감지해서 에러가 발생했다는 action을 dispatch
    yield put({
      type: GET_POST_ERROR,
      error: true,
      payload: e,
      meta: id
    });
  }
}

dispatch(getPost(1));

TypeScript


라이브러리를 설치할 땐 어떤 타입스크립트를 자체적으로 지원되거나 따로 설치해야 하는 라이브러리들이 존재함. 확인하려면 node_modules 안에 우리가 설치한 라이브러리리 디렉토리를 열어봤을 때 index.d.ts가 있으면 자체적으로 타입스크립트가 지원되는 라이브러리임.

없으면 따로 설치를 해야 지원받을 수 있음. 이렇게 설치하면 해당 라이브러리에 타입스크립트가 공식적으로 지원되는 게 아닌 커뮤니티에서 만든 서드파티 라이브러리를 사용해 타입을 지원받는 것임.

npm i @types/react-redux
# or
yarn add @types/react-redux

만약 공식적으로 타입스크립트가 지원이 되지 않으면 types 라이브러리가 있는지 없는지 확인하려면 npm에서 @types/libraryname 을 검색하기

profile
꾸준히 자유롭게 즐겁게

0개의 댓글