React API 연동

박종한·2020년 2월 5일
15

React

목록 보기
9/17

API가 필요한 이유

ReactFront-end에서 사용
그 말은 즉, 데이터를 모두가 사용할 수 있게 저장할 수 없다는 것을 의미하고, 또 그것은 다른 사용자와의 데이터 공유가 이뤄질 수 없음을 의미

따라서 API Server를 구축해 쓰거나, 남이 만든 것을 가져와서 쓰면, 값을 조작할 경우 API Server가 갱신이 되면서, 같은 API Server를 공유하는 유저들 또한 바뀐 값을 사용할 수 있게 됨

yarn add axios

axiosREST API에 데이터를 요청할 때, 이를 Promise로 처리할 수 있게 해주는 라이브러리

Rest API

HTTP method를 통해 서버와 클라이언트의 통신을 할 경우에 사용해서 상대가 어떤 통신을 요청하는지, 그리고 내가 어떤 통신을 요청해야 하는지 정할 수 있음

  • GET - 데이터 조회
  • POST - 데이터 등록
  • PUT - 데이터 수정
  • DELETE - 데이터 제거
axios.get('/users/1');

이 경우는 users들 중에 1번째 user에 대한 정보를 불러와달라는 것을 의미, 물론 1번째가 아니라 id가 1인 유저가 될 수도 있고 그건 서버가 정해놓는 규칙이라 상황에 따라 다름 (axios.get('/articles/1')이면 1번 글을 불러오라는 의미 혹은, 1번 유저가 작성한 게시글을 모두 불러오라는 의미 등등 여러 개로 쓰일 수 있음)

axios.post('/users', {
  username: 'aaaaa',
  name: 'JohnQue',
  password: '1234567',
  email: 'donot@have.th.at',
});

이런식으로 작성되면, 이번에는 users에 해당 user객체를 신규 등록해달라는 의미일 수 있고,

axios.post('/articles', {
  username: 'aaaaa',
  contents: 'react is good~!',
});

이런식으로 작성되면, articles에 해당 article을 등록해달라는 의미로 사용될 수 있음
즉 서버가 어떤 규칙을 정해놓으면 클라이언트는 그것에 맞게 REST API를 통신하면 되고, 그것을 도와주는 것이 바로 axios라이브러리

하지만 우리는 보통 API Server를 직접 구축해놓거나 갖고 있지 않음. 따라서, 도움이 필요 아래의 주소 참조

https://jsonplaceholder.typicode.com/

여기서 users정보를 받아, 웹에 출력하는 실습을 진행해보겠음

실습

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

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

export default App;

App Component은 단순히 Users라는 Component를 호출함

그리고 Users.js같은 경우는 useAsync라는 커스텀 hook을 사용하는데 이것부터 구현하겠음 useReducer를 사용

따라서 reducer를 첫번째로 먼저 구현

import { useReducer, useEffect, useCallback } from 'react';

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}`);
  }
}

LOADING의 경우는 현재 API로부터 데이터를 읽어들이고 있을 때를 말함 화면에 로딩중...이 출력될 것임
SUCCESS의 경우, API로부터 데이터를 읽어들이고, 받아오기에 성공했을때 dispatch를 통해 호출됨
ERROR의 경우 API로부터 데이터 수집에 실패했을 경우, 호출됨

export function useAsync(callback, deps = [], skip = false) {
  const [state, dispatch] = useReducer(reducer, {
    loading: false,
    data: null,
    error: null,
  });

  const fetchData = useCallback(async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  }, [callback]);

  useEffect(() => {
    if (skip) return;
    fetchData();
    //eslint-disable-next-line
  }, deps);

  return [state, fetchData];
}

먼저 useReducer부분 부터 설명

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

state의 초기값으로 객체를 넣어줬고, loadingfalse 나머지 data, errornull로 초기화하고 만들어둔 reducer함수 넣어줌

  const fetchData = useCallback(async () => {
    dispatch({ type: 'LOADING' });
    try {
      const data = await callback();
      dispatch({ type: 'SUCCESS', data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e });
    }
  }, [callback]);

asyncawait가 사용되었는데, async함수에서는 await가 실행되면, 그것이 끝날 때까지 다른 것들이 돌아가지 못함
여기서 callback함수는 useAsyncHook의 매개변수에 들어있는데, 이게 API와 통신하는 부분으로, Error가 발생하지 않는다면, 무사히 const data부분에 데이터를 받아올 것이고, 그걸 dispatch를 통해, 객체형태로 값을 받아옴

  useEffect(() => {
    if (skip) return;
    fetchData();
    //eslint-disable-next-line
  }, deps);

처음부터 웹페이지를 켰을때 바로 회원정보를 불러오는 것이 아니라 버튼을 통해 불러오도록 하고 싶기 때문에, skiptrue면 위의 fetchData를 실행하지 않고 false에서만 실행

이때 deps에 밑줄이 그어지면서 뭐가 잘못됐단 식으로 나오는데 위에
//eslint-disable-next-line를 넣어서 없애줌 이 부분을 왜 없애야 하는지 나중에 추가하겠음

useEffect도 아직 다루지 않았는데 이 글이 끝나는대로 다룰 예정

전부 완료 되었으면 마지막으로 [state, fetchData]를 반환
다음으로는 Users.js파일을 생성

import React, { useState } from 'react';
import axios from 'axios';
import { useAsync } from './useAsync';
import User from './User';

async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users',
  );
  return response.data;
}
function Users() {
  const [state, refetch] = useAsync(getUsers, [], true);
  const [userId, setUserId] = useState(null);
  const { loading, data: users, error } = state;

  if (loading) return <div>로딩중 ...</div>;
  if (error) return <div>에러!!!</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;

  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );
}

export default Users;

하나하나 설명하겠음

async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users',
  );
  return response.data;
}

이 비동기 함수는 아까, fetchData

const data = await callback();

이 부분의 callback에 해당하는 부분
즉, getUsers를 통해 API의 데이터를 받아오는 걸 알 수 있음

const [state, refetch] = useAsync(getUsers, [], true);

이렇게 호출을 하게 되면, useAsyncHook에서 반환해주는 [state, fetchData][state, refetch]에 들어가게 됨

이 말이 무슨 뜻이냐면, state에는 API와 통신되어 반환된 데이터가 들어가있다는 말이고, refetchAPI와 통신을 수행하는 fetchDataUsers.js에서 호출할 수 있다는 말이 됨

const [userId, setUserId] = useState(null);

이 부분은 단지 useState니 설명 생략

 const { loading, data: users, error } = state;

이 부분이 조금 특이한데, data: users이렇게 해주면, state에 들어있는 datadata로 들어가는게 아니라 users로 들어감

const x = {y: 1, z:[1,2,3]};
const {y, z: a} = x;
console.log(y); // 1
console.log(z); // Error
console.log(a); // [1,2,3]

오히려 z를 호출하면 Error가 발생 심지어 에러 이유는 z is not defined가 출력됨

  if (loading) return <div>로딩중 ...</div>;
  if (error) return <div>에러!!!</div>;
  if (!users) return <button onClick={refetch}>불러오기</button>;

이 부분은 loadingtrue일 때는, 실제로 API에서 정보를 받아오고 있을 타이밍이므로 로딩중 ...이 출력되고, errornull이 아닐 때는 뭔가 문제가 발생했다는 의미고, !usersnull인 것은 아직 버튼을 만들어둔 채로 API와 Connection을 진행하지 않았다는 의미이므로 버튼 클릭시 refetch함수가 실행되게끔 만듦

요약하면, 문제가 발생하거나 API와 통신 진행중의 경우는 단순히 로딩중 혹은 에러 라는 문자열이 뜨고, API와 아직 통신진행을 하지 않았다면 불러오기라는 문자열이 적힌 버튼이 생기고 그걸 클릭시 통신진행

  return (
    <>
      <ul>
        {users.map(user => (
          <li key={user.id} onClick={() => setUserId(user.id)}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      <button onClick={refetch}>다시 불러오기</button>
      {userId && <User id={userId} />}
    </>
  );

ul로 리스트를 만들어주고, 안의 내용을 li로 채워주는데, API로부터 받아온 users를 사용해서 채워줌. 고유값인 key값도 넣어주고, li를 클릭할 시에 유저에 관련된 정보가 뜨도록 하기 위해 useStateset도 사용해줌

정보를 받아온 상태에서도 다시 불러오기버튼의 refetch함수를 통해 데이터를 다시 받아올 수 있음 그리고 userIdsetUserId에 의해 설정되었다면, User Component를 렌더링 해줄 수 있게 됨

다음은 User.js

import React from 'react';
import axios from 'axios';
import { useAsync } from './useAsync';

async function getUser(id) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`,
  );
  return response.data;
}

function User({ id }) {
  const [state] = useAsync(() => getUser(id), [id]);
  const { loading, data: user, error } = state;

  if (loading) return <div>로딩중..</div>;
  if (error) return <div>에러가 발생했습니다.</div>;
  if (!user) return null;

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email: </b>
        {user.email}
      </p>
    </div>
  );
}

export default User;

설명 바로 들어가겠음

async function getUser(id) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`,
  );
  return response.data;
}

이 부분은 Users.js에서의 getUsers처럼 API와 통신하는 부분인데, Users.js에서 {userId && <User id={userId} />}이 부분을 통해 props에 id를 보내줬음. 때문에 그 id를 Literal Template(`)를 통해 ${id}User들 마다 값을 다르게 설정해 API와 통신할 수 있음 (클릭되는 li`에 따라 값이 변하니까)

즉, 아까는 유저들의 모든 정보를 받아왔다면 이번엔 단 한 명의 유저에 대한 정보만 받아옴

그 밑에 if문 들은 전의 Users.js와 내용이 비슷한데, 딱 하나 다른게 있음

if (!user) return null;

이 부분임
왜 다른거냐면, Users.js는 처음에 값을 받아오지 않도록 설정하고 버튼을 통해 받아올 수 있게 했는데 이건 그냥 li를 클릭만해도 알아서 받아오기 때문에 이 User Component가 렌더링 되었는데 !user라는건 불가능

  return (
    <div>
      <h2>{user.username}</h2>
      <p>
        <b>Email: </b>
        {user.email}
      </p>
    </div>
  );

그래서 그렇게 받은 걸 위 처럼 출력 해주면 결과물은 다음과 같음


이 모든 내용은 velopert님의 강좌를 보고 따라 만든 것일 뿐이므로, 정말 제대로 된 설명을 원하면
https://react.vlpt.us
에 방문하거나 패스트캠퍼스를 통해 강의를 결제하고 영상을 보길 추천

profile
코딩의 고수가 되고 싶은 종한이

0개의 댓글