API server data를 Redux로 받아오는 방법(Redux refactoring)

이예빈·2022년 9월 7일
1

JavaScript

목록 보기
20/26
post-thumbnail

요즘은 새로운 기술을 배울 때마다 전에 만들어놨던 웹 애플리케이션을 리팩토링 하는데 재미가 들렸다. 얼마전에는 과제로 전에 만들었던 JavaScript 기반의 웹 과제를 React로 리팩토링했고
Redux 기능을 추가해봤는데 API server data를 받아오는 과정이 좀 어려웠어서 글로 정리해보고자 한다.

처음 JavaScript로 과제 했을 당시 배포했던 시연 화면이다..ㅎ


Redux 사용 전

처음 화면을 실행했을 때 API server에서 받아온 discussions data가 렌더링 되어야 했다.

Redux를 사용하기 전에는 아래의 방식으로 코딩했다.

1. App.js에서 useEffect를 사용하여 데이터 fetch
2. useState의 state 변경 함수를 이용하여 state 세팅
3. 해당 state를 Disccussions 컴포넌트를 통해서 Discussion 컴포넌트에 Props로 전달

Discussions 컴포넌트는 Discussion 컴포넌트를 감싸는 wrapper의 역할만 하는(discussion data가 필요 없는) 컴포넌트였는데
Discussion 컴포넌트에 props를 전달해주기 위해 불가피하게 props를 받아왔던 것이다.


Redux 사용 후

Redux를 설치한 후에 기본적인 설정들(react-redux의 Provider 컴포넌트로 App 컴포넌트 내부 감싸주기, Provider에 store 속성 넣어주기 등...)을 해주고 아래와 같이 코드를 완성했다.

1. 📁 actions/actions.js

export const ADD_DISCUSSION = 'ADD_DISCUSSION';
export const FETCH_DISCUSSIONS_REQUEST = 'FETCH_DISCUSSIONS_REQUEST';
export const FETCH_DISCUSSIONS_ERROR = 'FETCH_DISCUSSIONS_ERROR';
export const FETCH_DISCUSSIONS_SUCCESS = 'FETCH_DISCUSSIONS_SUCCESS';

export const addDiscussion = (author, title, createdAt) => {
    let avatarUrl = 'https://p.kindpng.com/picc/s/33-338711_circle-user-icon-blue-hd-png-download.png';
  // unknown user avatar img
    return {
        type: ADD_DISCUSSION,
        payload: {
            author,
            title,
            createdAt,
            avatarUrl,
        }
    }
}

export const fetchDiscussionsRequest = () => {
  return {
    type: FETCH_DISCUSSIONS_REQUEST
  }
}

export const fetchDiscussionsError = (error) => {
  return {
    type: FETCH_DISCUSSIONS_ERROR,
    payload: error
  }
}

export const fetchDiscussionsSuccess = discussions => ({
  type: FETCH_DISCUSSIONS_SUCCESS,
  payload: {discussions}
})

actions.js 파일에는 아래 4가지 액션을 구성하였다.

  1. 새로운 discussion을 추가할 때 실행할 addDiscussion
  2. discussions data를 fetch 요청할 때 실행할 fetchDiscussionsRequest
  3. fetch를 실패했을 때 실행할 fetchDiscussionsError
  4. fetch를 성공했을 때 data를 payload로 전달해줄 fetchDiscussionsSuccess

// 본 내용에서는 fetch 내용(2~4번)만 다룰 것이다


1-1. 📁 actions/fetchDiscussions.js

import axios from 'axios';
import { fetchDiscussionsRequest, fetchDiscussionsSuccess, fetchDiscussionsError } from './actions';

export const fetchDiscussions = () => {
    return dispatch => {
        dispatch(fetchDiscussionsRequest());
        axios.get('http://localhost:4000/discussions/')
        .then(res => {
            dispatch(fetchDiscussionsSuccess(res.data));
        })
        .catch(error => {
            dispatch(fetchDiscussionsError(error));
        });
    };
}

API server에서 데이터를 fetch해오기 위해서 axios라는 라이브러리를 사용했다.
fetchDiscussions라는 함수를 실행했을 때 fetch request action을 dispatch해주고
axios를 통해 server의 data를 get해온다.
➡️ 성공시: fetch success action과 data를 dispatch 함.
➡️ 실패시: fetch error action과 error를 dispatch함.

Axios는 브라우저와 node.js에서 사용할 수 있는 Promise 기반의 HTTP 클라이언트 라이브러리이다. 동일한 코드 베이스로 브라우저와 node.js에서 실행할 수 있다.


2. 📁 reducers/discussionReducer.js

import { ADD_DISCUSSION, FETCH_DISCUSSIONS_SUCCESS,FETCH_DISCUSSIONS_REQUEST, FETCH_DISCUSSIONS_ERROR } from "../actions/actions";

let initialState = {
  discussions: [],
  error: null
}

export const discussionReducer = (state = initialState, action) => {
  switch(action.type){
    case FETCH_DISCUSSIONS_REQUEST:
      return {...state};
    case FETCH_DISCUSSIONS_SUCCESS:
      return {...state, discussions: action.payload.discussions};
    case FETCH_DISCUSSIONS_ERROR:
      return {...state, error: action.payload}
    case ADD_DISCUSSION:
      return {...state, discussions: [action.payload, ...state.discussions]}
    default:
      return state;
  }
};

export default discussionReducer;

Reducer에서는 초기값을 간단하게 설정해주고
전달받는 action type에 따라 return 값을 설정해주었다.

fetch 성공시 기존 state 객체와 state.discussions에 payload.discussions를 할당한 새로운 state 객체를 리턴하고
fetch 실패시 기존 state 객체와 state.error에 payload를 할당한 새로운 state 객체를 리턴한다.


3. 📁 store/store.js

import { legacy_createStore as createStore, applyMiddleware, compose } from 'redux';
import { discussionReducer } from '../reducers/discussionReducer'
import thunk from 'redux-thunk';

const devTools = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
const composeEnhancers = devTools || compose;

export const store = createStore(discussionReducer, composeEnhancers(applyMiddleware(thunk)));

export default store;

server data를 요청해오기 위해서는 비동기 처리를 해줘야 하므로 redux-thunk 미들웨어의 thunk를 import 해 주었다.
redux-thunk를 이용하여 객체 대신에 함수를 생성하는 action creator를 작성할 수 있다.

compose는 인자로 전달된 함수를 조합해주는 역할을 하는 함수이다.
해당 코드에서는 applyMiddleware(thunk)만 인자로 전달되었지만 뒤에 여러 미들웨어를 추가하여 조합하는 데에 사용할 수 있다.

추가로 크롬 브라우저의 확장 프로그램으로 Redux devTools가 설치되어 있는 경우 redux의 compose 대신에__REDUX_DEVTOOLS_EXTENSION_COMPOSE__함수를 대신 사용할 수 있도록 설정 해주었다.


4. 📁 components/Discussion.js

import { useSelector } from "react-redux";
import store from "../store/store";
import { fetchDiscussions } from "../actions/fetchDiscussions"

store.dispatch(fetchDiscussions());

export const Discussion = () => {
  const state = useSelector(state => state.discussions);
  const discussions = state;

  return (
    <ul className="discussions__container">
      {server data인 discussions를 map메서드를 통해 DOM으로 뿌려주는 코드};

export default Discussion;

fetch해온 데이터를 실제로 사용해야 하는 Discussion 컴포넌트이다.
파일의 상단에서 store.dispatch 메서드를 이용하여 fetchDiscussions action을 실행한다.
➡️ state가 변경된다.
fetch한 server data(state)를 DOM으로 뿌려준다.


5. 📁 components/Discussions.js

redux를 사용함으로써 Discussions 컴포넌트에서 불필요하게 data를 props로 받을 필요가 없어졌다.


6. 📁 App.js

App.js 파일도 아주 깔끔하게 정리되었다.



reference

https://axios-http.com/docs/intro
https://redux-advanced.vlpt.us/4/2.html
https://redux.js.org/api/applymiddleware/
https://velopert.com/3401

profile
temporary potato

0개의 댓글