본문 바로가기
  • 하고 싶은 일을 하자
개발

[코드숨 리액트 강의] 5주차 회고 - 비동기 코드에서 Thunk를 쓰는 이유

by 박빵떡 2022. 6. 6.
반응형

드디어 강의의 절반을 넘게 들었다!

8주차까지면 끝인데 벌써 5주차가 끝나다니 시간 참 빠르다.

 

이번주는 수요일 휴일이 있어서 여유가 있었다.

redux와 비동기 fetch를 TDD로 개발하는 것을 배웠다.

지난번 첫 TDD를 개발해서 그런가 이번 과제는 처음 TDD를 접했을 때 보단 수월했다.

 

과제를 월요일부터 시작해서 제출하면 더 좋을 것 같다.

코드 리뷰를 좀 더 받는게 나에게 큰 도움이 될 것 같다.

-> 내일부터 과제 해보자!

 

Fetch 테스트

jest로 테스트하는 것은 런타임 때 테스트하는 것이 아니기 때문에

실제로 fetch 이후 response를 받아서 테스트가 진행되지는 않는다.

useSelector.mockImplementation((selector) => selector({
    categories: [{ id: 1, name: '한식' }],
}));

위와 같이 mockImplementation 을 통해 fetch 이후의 response를 받았다고 가정한 뒤에 테스트를 진행할 수 있다.

 

Async Await

비동기는 역시나 Async Await을 사용하면 된다.

 

Redux Thunk Middleware

일반적으로 Redux에서 비동기 작업을 할 땐 Thunk라는 Middleware를 사용한다.

하지만 Thunk를 사용하지 않고도 할 수 있다. 아래는 Thunk를 사용하지 않은 예시이다.

<App.js>
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';

async function loadCategories({ dispatch }) {
  const categories = await fetchCategories();
  dispatch(setCategories(categories));
};

function fetchCategoreis() {
  const url = 'https://example';
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

export default function App() {
  const dispatch = useDispatch();
  const categoreis = useSelector((state) => {
    categories: state.categories,
  });
  
  useEffect(() => {
    loadCategories({ dispatch });
  }, []);

  return (
    <div>
      {categories.map((category) => category.name)}
    </div>
}

<reducer.js>
// 액션 타입
export const SET_CATEGORIES = 'SET_CATEGORIES';

// 액션 생성 함수
const setCategories = (categories) => ({ type: SET_CATEGORIES, payload: { categories } });

export default function reducer(state = initialState, action) {
  switch (action.type) {
  case SET_CATEGORIES: {
    const { categories } = action.payload;
    return {
      ...state,
      categories,
    };
  }
  
  default: {
    return state;
  }
  }
}

우리가 최종적으로 하려는 것은 무언가를 dispatch 하여 redux의 상태를 바꾸는 것이다.

기본적으로 dispatch의 인자는 객체가 들어온다.

dispatch({ type: SET_CATEGORIES, payload: { categories } });

 

그런데 dispatch 의 인자로 객체가 아니라 함수를 넣고

그 함수에서 이런 저런 기능들을 수행한 뒤 마지막에 객체를 리턴해주면 어떨까?

라는 생각에서 만들어진 것이 Thunk 이다.

 

Thunk를 사용한다면 아래처럼 코드가 변경된다.

<App.js>
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { loadCategories } from './redux/reducer';

export default function App() {
  const dispatch = useDispatch();
  const categoreis = useSelector((state) => {
    categories: state.categories,
  });
  
  useEffect(() => {
    dispatch(loadCategories());
  }, []);

  return (
    <div>
      {categories.map((category) => category.name)}
    </div>
}

<reducer.js>
// 액션 타입
export const SET_CATEGORIES = 'SET_CATEGORIES';

// 액션 생성 함수
const setCategories = (categories) => ({ type: SET_CATEGORIES, payload: { categories } });

export function loadCategories() {
  return async (dispatch) => {
    const categories = await fetchCategories();
    dispatch(setCategories(categories));
  };
}

export default function reducer(state = initialState, action) {
  switch (action.type) {
  case SET_CATEGORIES: {
    const { categories } = action.payload;
    return {
      ...state,
      categories,
    };
  }
  
  default: {
    return state;
  }
  }
}

Thunk를 사용하면  App.js 에서 dispatch(loadCategories())로 loadCategories 함수가 실행되고

비동기 작업을 수행한 뒤에

dispatch(setCategories(categories)) 로 redux의 상태를 바꾸는 것을 알 수 있다.

 

그럼 Thunk를 쓰지 않고도 구현할 수 있는데 Thunk를 쓰는 이유는 무엇인가?

Thunk를 이용하면 Thunk를 사용하지 않았을 때는 하기 힘든 기능들을 구현할 수 있다.

<Logger.js>
const logger = store => next => action => {
  console.group(action.type);
  console.log('The action: ', action);
  const result = next(action);
  console.log('The new state:', store.getState());
  console.groupEnd();
  return result;
};

export default logger;

<index.js>
import { applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import logger from './logger';

export default applyMiddleware(thunk, logger);

logger라는 Thunk를 만들어 미들웨어에 적용하면 dispatch 할 때 action과 state를 log로 출력할 수 있다.

이러한 기능은 Thunk를 이용하지 않으면 어렵다. 불가능하진 않겠지만 (action은 reducer에서 출력하고, state는 app에서 useSelector로 전체 state를 출력하고) 굉장히 번거롭다.

 

비동기 코드는 Thunk를 쓰는 방법과 쓰지 않는 방법 2가지로 구현이 가능하다.

위의 Logger 예시를 보면 알 수 있듯이 비동기를 Thunk로 구현하면 더 다양한 기능을 쉽게 구현할 수 있다.

예를 들어 특정 액션일 때만 비동기 요청을 수행하는 기능을 Thunk에서 구현이 가능하다.

내가 위에 적은 category 예시는 간단한 경우라 Thunk를 사용하지 않아도 되지만

1. Thunk를 이용해 더 다양한 기능을 사용

2. Thunk를 설치하여 사용 중이라면 코드의 통일성을 위해 Thunk를 통해 비동기 기능을 개발

등의 이유로 Thunk를 선택하여 비동기 기능을 개발할 수 있다.

 

즉 Thunk를 이용하느냐 마느냐는 개발자 개인의 판단으로 선택하면 된다.

반응형

댓글