api 연동

» REACT

api 연동이란?

  • axios 라이브러리 설치

    yarn add axios
    
  • 사용법

    import axios from "axios";
    
    axios.메소드방식("API주소", { 등록할정보 });
    /* axios.get('/users/1'); */
    

    예시

    axios.post("/users", {
      username: "kaen",
      name: "sunghun",
    });
    
  • API를 요청할때 관리 순서

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

rest api

클라이언트와 서버가 소통하는 방식으로 HTTP 메서드 방식으로 소통

  • HTML 메소드 방식 종류 |메소드|역할| |-|-| |GET|데이터 조회| |POST|데이터 등록| |PUT|데이터 수정| |DELETE|데이터 제거|

  • 메소드 예시 |예시|설명| |-|-| |GET /users|모든 사용자의 계정을 반환| |GET /users/1|아이디가 1인 계정 데이터를 반환| |POST /articles|특정 정보를 등록| |GET /articles|모든 게시글 정보를 불러옴| |GET /articles/1|아이디가 1인 게시글 정보를 불러옴| |PUT /articles/1|아이디가 1인 게시물을 수정| |DELETE /articles/1|아이디가 1인 게시물을 삭제|

컴포넌트에서 API로 데이터를 요청하는 기본적인 방법은 useState와 useEffect로 데이터를 로딩하는 것

useState로 구현

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 {
      setUsers(null);
      setError(null);
      setLoading(true);
      // await사용
      const response = await axios.get(
        "https://jsonplaceholder.typicode.com/users/"
      );
      // 결과값을 확인
      setUsers(response.data);
    } catch (e) {
      // 발생한 에러값을 받아줌
      setError(e);
    }
    // 로딩이 끝났다고 알림
    setLoading(false);
  };

  // 처음 렌더링 될때 API를 요청
  useEffect(() => {
    // 함수 호출
    fetchUsers();
  }, []);

  // 만약 로딩 중이라면
  if (loading) return <div>로딩중..</div>;
  // 만약 에러가 있다면
  if (error) return <div>에러가 발생했습니다</div>;
  // 만약 로딩이 끝났지만 users가 유효하지 않다면
  if (!users) return null;

  return (
    <>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.username} ({user.name})
          </li>
        ))}
      </ul>
      {/* 누를때마다 API함수가 호출됨 */}
      <button onClick={fetchUsers}>다시 불러오기</button>
    </>
  );
}

export default Users;

useReducer로 구현

요청 관리 로직을 분리해서 재사용할 수 있는 장점이 있음(커스텀 hook)

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

// LOADING, SUCCESS, ERROR reducer 함수를 생성
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 값 사용
    dispatch({ type: "LOADING" });
    try {
      const response = await axios.get(
        "https://jsonplaceholder.typicode.com/users/"
      );
      // dispatch 값 사용
      dispatch({ type: "SUCCESS", data: response.data });
    } catch (e) {
      // dispatch 값 사용
      dispatch({ type: "ERROR", error: e });
    }
  };

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

  // 비구조화 할당
  const { loading, data: users, error } = state; // data: users로 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;

커스텀 Hook 구현

// 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; // data: users로 data가 users 값이 되게 해줌

  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;
// 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;
// useAsync.js
import React, { useReducer, useEffect, useCallback } from "react";

// LOADING, SUCCESS, ERROR reducer 함수를 생성
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}`);
  }
}

// 콜백은 API를 호출하는 함수를 넣어줄것
// deps는 컴포넌트가 로딩 혹은 변경됬을때 요청
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();
  }, deps);

  return [state, fetchData];
}

export default useAsync;

react-async로 구현

useAsync와 비슷한 라이브러리

  • 설치

    yarn add react-async
    
  • 사용법 react-asyncuseAsync를 사용할 때 파라미터로 넣는 옵션 객체에는 호출 할 함수 promiseFn을 넣고, 파라미터도 필드 이름과 함께 (customerId)를 넣어줘야 함

    // 공식문서
    import { useAsync } from "react-async";
    const loadCustomer = async ({ customerId }, { signal }) => {
      const res = await fetch(`/api/customers/${customerId}`, { signal });
      if (!res.ok) throw new Error(res);
      return res.json();
    };
    
    const MyComponent = () => {
      const { data, error, isLoading } = useAsync({
        promiseFn: loadCustomer,
        customerId: 1,
      });
      if (isLoading) return "Loading...";
      if (error) return `Something went wrong: ${error.message}`;
      if (data)
        return (
          <div>
            <strong>Loaded some data:</strong>
            <pre>{JSON.stringify(data, null, 2)}</pre>
          </div>
        );
      return null;
    };
    

    사용자의 특정 액션에 따라 API를 호출하고 싶을 땐 prmiseFn대신 deferFn을 사용하고 reload대신 run을 사용하면 됨

Context로 구현

현재 로그인된 사용자의 정보나 설정 등 특정 데이터들을 Context API로 관리하는게 편함