React 열공~! #24

#24 빡 집중!

API 연동하기

이번에는 리액트 애플리케이션에서 애플리케이션에서 API 를 연동하는 방법을 배우게 될 것이다.

우리가 앱 애플리케이션을 만들면 데이터를 브라우저에서만 들고 있는게 아니라, 데이터를 보존시키고 다른 사람들도 조회 할 수 있게 하려면 서버를 만들고 서버의 API 를 사용해서 데이터를 읽고, 써야 한다.

실제 프로젝트에서는 주로 Redux 라는 라이브러리와 Redux 미들웨어를 함께 사용해서 구현을 하는 경우가 많다. 이 것에 대해서는 나중에 알아볼 것이고 이 도구들은 API 연동을 할 때 필수적인 요소는 아니다.

이번에 API 연동을 배우게 될 떄에는 Redux 없이, 그냥 컴포넌트에서 API 연동을 하게 될 때 어떻게 해야 하는지 알아보고 더 깔끔한 코드로 구현하는 방법도 다뤄보도록 하겠다.

API 연동의 기본

API 연동을 위해서, 우선 새로운 프로젝트를 생성해준다.

$ npx create-react-app api-integrate

그리고, API 를 호출하기 위해서 axios 라는 라이브러리를 설치해준다.

$ cd api-integrate
$ npm install axios

우리는 axios 를 사용해서 GET, PUT, POST, DELETE 등의 메서드로 API 요청을 할 수 있다.

REST API 를 사용 할 때에는 하고 싶은 작업에 따라 다른 메서드로 요청을 할 수 있는데 메서드들은 다음 의미를 가지고 있다.

  • GET: 데이터 조회
  • POST: 데이터 등록
  • PUT: 데이터 수정
  • DELETE: 데이터 제거

참고로 이 메소드 외에 PATCH, HEAD 와 같은 메서드들도 존재한다.

axios 의 사용법은...

import axios from 'axios';

axios.get('/users/1');

get 이 위치한 자리에는 메서드 이름을 소문자로 넣어준다. 그리고 새로운 데이터를 등록하고 싶으면 axios.post( ) 를 사용하면 된다.

그리고, 파라미터에는 API 의 주소를 넣어준다.

axios.post( ) 로 데이터를 등록 할 때에는 두번째 파라미터에 등록하고자 하는 정보를 넣을 수 있다.

axios.post('/users', {
  username: 'blabla',
  name: 'blabla'
});

이번에 API 연동 실습을 할 때에는 JSONPlaceholder 에 있는 연습용 API 를 사용해볼 것 이다.

사용 할 API 주소는 아래를 참고하면 된다.

https://jsonplaceholder.typicode.com/users

안에 형식은 다음과 같이 이루어져있다.

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "[email protected]",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "[email protected]",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  (...)
]

useState 와 useEffect 로 데이터 로딩하기

이번에는 useState 를 사용하여 요청 상태를 관리하고, useEffect 를 사용해서 컴포넌트가 렌더링되는 시점에 요청을 시작하는 작업을 해보겠다.

우리는 요청에 대한 상태를 관리할 때는 다음과 같이 3가지 상태를 관리해야 한다.

  1. 요청의 결과
  2. 로딩 상태
  3. 에러

src 디렉토리에 Users.js 를 생성하고 다음과 같이 코드를 작성해준다.

Users.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        // 요청이 시작 할 때에는 error 와 users 를 초기화하고
        setError(null);
        setUsers(null);
        // loading 상태를 true 로 바꿉니다.
        setLoading(true);
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    };

    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>
          {user.username} ({user.name})
        </li>
      ))}
    </ul>
  );
}

export default Users;

참고로 useEffect 에서 첫 번째 파라미터로 등록해준 함수는 async 를 사용할 수 없기 때문에 함수 내부에서 async 를 사용하는 새로운 함수를 선언해준다.

로딩 상태가 활성화 됐을 땐 로딩중.. 이라는 문구를 보여주고,

그리고, users 에 값이 아직 없을 때에는 null 을 보여주도록 처리해두었다.

마지막에서는 users 배열을 렌더링하는 작업을 해주었다.

이제 이 컴포넌트가 잘 작동되는지 확인해보자!

App 컴포넌트에서 User 컴포넌트를 렌더링해보자

App.js

import React from 'react';
import Users from './Users';

function App() {
  return <Users />;
}

export default App;

출처 : 벨로퍼트와 함께하는 모던 리액트

잘 작동된 것을 확인할 수 있다.

에러 발생 확인하기

한 번 우리는 에러가 발생하는지도 확인해보자!

에러가 발생하는 것을 확인하기 위해 주소를 이상하게 바꿔보자...

const response = await axios.get(
  'https://jsonplaceholder.typicode.com/users/showmeerror'
);

에러가 발생했다고 문구가 나타난다.

버튼을 눌러서 API 재요청하기

이번에는 버튼을 눌러서 API 를 재요청하는 기능을 구현해보겠다.
그렇게 할려면, 아까 만들어준 fetchUsers 함수를 바깥으로 꺼내주고, 버튼을 만들어서 해당 함수를 연결해주면 된다.

Users.js

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
  const [users, setUsers] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const fetchUsers = async () => {
    try {
      // 요청이 시작 할 때에는 error 와 users 를 초기화하고
      setError(null);
      setUsers(null);
      // loading 상태를 true 로 바꿉니다.
      setLoading(true);
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      setUsers(response.data); // 데이터는 response.data 안에 들어있습니다.
    } catch (e) {
      setError(e);
    }
    setLoading(false);
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

결과는...

출처 : 벨로퍼트와 함께하는 모던 리액트

버튼을 클릭을하면 fetchUsers 함수가 재실행된다.

useReducer 로 요청 상태 관리하기

이번에는 전에 구현했던 User 컴포넌트에서 useState 대신에 useReducer 를 사용해서 구현을 해보도록 하겠다.

useReducer 를 사용하여 LOADING, SUCCESS, ERROR 를 액션에 따라 다르게 처리를 해보자!

Users.js

import React, { useEffect, useReducer } from 'react';
import axios from 'axios';

function reducer(state, action) {
  switch (action.type) {
    case 'LOADING':
      return {
        loading: true,
        data: null,
        error: null
      };
    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,
        error: null
      };
    case 'ERROR':
      return {
        loading: false,
        data: null,
        error: action.error
      };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

function Users() {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null
  });

  const fetchUsers = async () => {
    dispatch({ type: 'LOADING' });
    try {
      const response = await axios.get(
        'https://jsonplaceholder.typicode.com/users'
      );
      dispatch({ type: 'SUCCESS', data: response.data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  const { loading, data: users, error } = state; // state.data 를 users 키워드로 조회

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다</div>;
  if (!users) return null;
  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

useReducer 로 구현했을 때 장점은, useState 에서 setState 함수를 여러번 사용하지 않아도 된다는 점과 리듀서로 로직을 분리했으니 다른곳에서도 쉽게 재사용을 할 수 있다는 점 이다.

물론, 개인 취향에 맞게 useState 를 구현해도 상관없다.

다음에는 이번에 만든 reducer 를 기반으로, 커스텀 Hook 을 만들어보겠다.

참고 : 벨로퍼트와 함께하는 모던 리액트

느낀점 :

  • 오늘은 API 연동하는 방법과 useReducer 를 useState 대신해 상태 요청을 해보았다.
  • useState 와 useReducer 는 각자마다 장단점이 있으니 잘 생각하고 프로젝트를 구현해야겠다!

좋은 웹페이지 즐겨찾기