천진난만 코딩 스토리

2023.03.11) 항해 34일차 (3- 비동기 통신 - axios) 본문

TIL(Today I Learned)

2023.03.11) 항해 34일차 (3- 비동기 통신 - axios)

Wisdom_1104 2023. 3. 12. 14:06

드디어 걱정되던 비동기 시작......!!!

내용 자체는 크게 어렵지 않았다.

다만 포트를 잘못 연결해서 오류가 나거나

깃 하면서 오류가 났어서 그렇지........

강의에는 없는 내용인 인풋에서 값 입력 후 버튼을 누르면 인풋을 빈 값이 되도록 구현도 하였다.

오늘은 좀 늦게까지 공부하느라 힘들긴 했지만 뿌듯하니 됐당!!!!!!!!

 

 

 

*이번 내용에서 사용하게 될 기본 명령어 모음!

yarn add react-redux @reduxjs/toolkit //리덕스 툴킷 설치
yarn add json-server //json-server 설치
yarn json-server --watch db.json --port 4000 //json-server의 4000 port 실행

1) Axios

공식문서에 따르면 axios 란 node.js와 브라우저를 위한 Promise 기반 http 클라이언트 라고 한다.

즉, http를 이용해서 서버와 통신하기 위해 사용하는 패키지이다.

 

① Axios 설치하기

yarn add axios

설치 후 테스트를 해야 하니 json-server도 설치 후 실행해주자!

 

2) GET

① Axios get 사용방법

get은 서버의 데이터를 조회할 때 사용한다.

// url에는 서버의 url이 들어가고, config에는 기타 여러가지 설정을 추가할 수 있다.
// config는 axios 공식문서에서 확인하기!

axios.get(url[, config]) // GET

참고 공식문서 링크: https://axios-http.com/kr/docs/req_config

 

 

② 우리가 사용하는 json-server API 명세서 확인하기

우리가 Axios를 사용해서 GET 요청 코드를 작성하기에 앞서,

어떤 방식으로 요청 해야할지는 우리가 사용하는 json-server의 방식을 알아야 한다.

즉, Axios는 GET 요청을 할 수 있도록 도와주는 패키지일뿐이지,

“어떻게 요청을 해야하지?” 와 같은 방식에 대한 확인은 우리가 사용할 API 명세서를 보아야 한다.

ex) GET 요청을 할 때 path variable로 해야할지, query로 보내야할지는 API를 만든 사람이 하라는대로 해야 한다....!

 

json-server의 공식문서를 보면,

전체 정보나 상세 정보는 아래와 같이 path variable 로 url을 작성하면 된다고 한다.

그리고 filter와 같은 기능을 위해서 GET 요청을 하고자할 때는 query로 보내라고 한다.

 

③ 코드로 알아보기

코드를 통해 사용방법을 알아보자.

우리가 만든 json-server에 있는 todos를, axios를 이용해서 fetching하고 useState를 통해서 관리하는 로직이다. 

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

const App = () => {
  const [todos, setTodos] = useState(null);

  // axios를 통해서 get 요청을 하는 함수를 생성힌다.
  // 비동기처리를 해야하므로 async/await 구문을 통해서 처리한다.
  const fetchTodos = async () => {
    const { data } = await axios.get("http://localhost:3001/todos");
    setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set 한다.
  };

  // 생성한 함수를 컴포넌트가 mount 됐을 떄 실행하기 위해 useEffect를 사용한다.
  useEffect(() => {
    // effect 구문에 생성한 함수를 넣어 실행한다.
    fetchTodos();
  }, []);

  // data fetching이 정상적으로 되었는지 콘솔을 통해 확인한다.
  console.log(todos);
  return (
    <div>
      {todos?.map((item) => {
        return (
          <div key={item.id}>
            {item.id} : {item.title}
          </div>
        );
      })}
    </div>
  );
};

export default App;

콘솔로 결과를 확인하니, 우리가 생성한 Todos를 정상적으로 서버에서 가져와서 state가 set 한 것을 확인할 수 있다.

 

3) POST

① Axios post 사용방법

Axios POSTpost는 보통 서버에 데이터를 추가할 때 사용한다.

다만 post 요청에 대한 로직은 BE 개발자가 구현하는 것이기때문에 추가외에 다른 용도로 사용될 수 있지만,

보통은 클라이언트의 데이터를 body형태로 서버에 보내고자 할 때 사용한다.

axios.post(url[, data[, config]]) // POST

② 코드로 알아보기

화면에 인풋과 버튼이 있고, 인풋에 어떤 값을 넣고 버튼을 클릭했을 때 onSubmitHandler 이 실행된다.

onSubmitHandler 함수의 목적은 todo를 body에 담아 서버로 POST 요청을 보내는 것이다.

아까의 get을 사용했던 코드에 아래의 코드를 추가만 해 주었다!

const App = () => {
  const [todo, setTodo] = useState({
    title: "",
  });

  // 추가 함수
  const onSubmitHandler = async () => {
    axios.post(`${process.env.REACT_APP_SEVER_KEY}/todos`, todo);
    setTodos([...todos, todo]);
  };

  return (
    <>
      {/* Input영역 */}
      <form
        onSubmit={(e) => {
          // submit했을 때 브라우저의 새로고침을 방지한다.
          e.preventDefault();
          onSubmitHandler(todo);
        }}
      >
        <input
          type="text"
          onChange={(ev) => {
            const { value } = ev.target;
            setTodo({
              ...todo,
              title: value,
            });
          }}
        />
        <button>추가하기</button>
      </form>
      {/* 데이터 영역 */}
      <div>
        {todos?.map((todo) => (
          <div key={todo.id}>
            {todo.id} : {todo.title}
          </div>
        ))}
      </div>
    </>
  );
};

export default App;

 

4) DELETE

① Axios delete 사용방법

DELETE는 저장되어 있는 데이터를 삭제하고자 요청을 보낼 때 사용한다.

axios.delete(url[, config]) // DELETE

 

② 코드로 알아보기

onClickDeleteButtonHandler 와 map 을 돌린 항목별로 삭제하기 버튼을 추가해준다.

삭제 버튼을 누르면 삭제 된다.

기존 위의 코드들에 아래 코드만 추가해주었다.

 //삭제 함수
  const onClickDeleteButtonHandler = (id) => {
    axios.delete(`http://localhost:4000/todos/${id}`);
    setTodos(
      todos.filter((item) => {
        return item.id !== id;
      })
    );
  };

      <div>
        {todos?.map((todo) => (
          <div key={todo.id}>
            {todo.id} : {todo.title}
            //삭제 버튼 추가
            <button
              type="button"
              onClick={() => onClickDeleteButtonHandler(todo.id)}
            >
              삭제하기
            </button>
          </div>
        ))}
      </div>

 

5) PATCH

① Axios patch 사용방법

patch는 보통 어떤 데이터를 수정하고자 서버에 요청을 보낼 때 사용하는 메서드 이다.

다만, 이것은 http 환경에서 서로가 한 약속이자 문맥이기때문에,

수정을 하고자 반드시 patch, put 을 써야만 하는 것은 아니다.

BE에 의해서 POST를 통해서 “수정" 이라는 기능은 충분히 만들 수 있기 때문이다.

다만 이러한 약속들을 대부분의 개발자들이 지키고 있다는 점을 알고 있자!

axios.patch(url[, data[, config]])  // PATCH

 

② 코드로 알아보기

Todo를 수정하기 위해 필요한 데이터는 2개가 있다.

수정하고자하는 Todo의 id, 그리고 수정하고자 하는 값이다.

수정하고자 하는 값은 기존에 있던 todo라는 state를 사용하면 될 것이고,

id는 직접 입력을 해서 url로 넘겨주는 방식으로 구현했다.

마찬가지로 위의 코드들에 밑의 코드만 추가했다.

  const [targetId, setTargetId] = useState("");
  const [editTodo, setEditTodo] = useState("");

  //수정 함수
  const onClickEditButtonHandler = async () => {
    axios.patch(`http://localhost:4000/todos/${targetId}`, {
      title: editTodo,
    });
    setTodos(
      todos.map((item) => {
        if (item.id == targetId) {
          return { ...item, title: editTodo };
        } else {
          return item;
        }
      })
    );
    setTargetId("");
    setEditTodo("");
  };

  return (
        <div>
          <input
            type="text"
            placeholder="수정하고싶은 Todo ID"
            value={targetId}
            onChange={(e) => {
              setTargetId(e.target.value);
            }}
          />
          <input
            type="text"
            placeholder="수정값 입력"
            value={editTodo}
            onChange={(e) => {
              setEditTodo(e.target.value);
            }}
          />
          <button
            // type='button' 을 추가해야 form의 영향에서 벗어남
            type="button"
            onClick={() => onClickEditButtonHandler()}
          >
            수정하기
          </button>
        </div>

 

 

6) ENV

변수명은 반드시 'REACT_APP_'으로 시작해야 한다.

.gitignore에 env를 등록해야 한다.

기본으로 세팅이 되어있지만 확인해주도록 하자!!

소스코드에서 import 없이 process.env.REACT_APP_변수명으로 불러오면 적용된다.

만약 env의 값을 변경하여 적용하고 싶다면, 변경 후 server를 다시 실행시켜주어야 정상적으로 적용된다.

import React, { useEffect, useState } from "react";
import axios from "axios";
const REACT_APP_SEVER_KEY = "http://localhost:4000";

const App = () => {
  // 조회 함수
  const fetchTodos = async () => {
    const { data } = await axios.get(`${REACT_APP_SEVER_KEY}/todos`);
  };

  // 추가 함수
  const onSubmitHandler = async () => {
    axios.post(`${REACT_APP_SEVER_KEY}/todos`, todo);
  };

  //삭제 함수
  const onClickDeleteButtonHandler = async (id) => {
    axios.delete(`${REACT_APP_SEVER_KEY}/todos/${id}`);
  };

  //수정 함수
  const onClickEditButtonHandler = async () => {
    axios.patch(`http://localhost:4000/todos/${targetId}`, {
  };

 

 

 

 

7) instance와 interceptor

① axios interceptor

이름에서 알 수 있듯, 다음 두 상황에서 흐름을 가로채서 여러분이 어떠한 코드 상의 관여를 할 수 있게 한다.

  • 요청(request)이 처리되기 전( = http request가 서버에 전달되기 전)
  •  응답(response)의 then(=성공) 또는 catch(=실패)가 처리되기 전

따라서, 우리가 가정했던 상황들을 포함하여 요청 및 응답시에 필요한 작업들을 한꺼번에 처리를 할 수 있다.

  • 요청 헤더 추가
  • 인증 관리
  • 로그 관련 로직 삽입
  • 에러 핸들링

이러한 부분에서 빛을 발한다.

 

② 코드로 알아보기

❶ instance 만들기, baseURL 설정하기

const data = axios.get("<http://localhost:4000/>");

axios를 사용하는 방법으로 데이터 통신을 해왔다. 

완전히 plain axios, 순수 axios이다.

즉, custom 설정이 전혀 되어있지 않았는데, 이 axios를 인스턴스(instance)라고 한다.

이걸 변경하여 사용할 수 있다.

 

먼저, api 파일을 만든다.

//src > axios > api.js

import axios from "axios";

// axios.create의 입력값으로 들어가는 객체는 configuration 객체이다.

const instance = axios.create({
	baseURL: "http://localhost:4000",
});

export default instance;

다음 app 파일에 연결을 해준다.

import api from "./axios/api";

  // 조회 함수
  const fetchTodos = async () => {
    const { data } = await api.get("/todos");
  };

  // 추가 함수
  const onSubmitHandler = async () => {
    api.post("/todos", todo);
  };

  //삭제 함수
  const onClickDeleteButtonHandler = async (id) => {
    api.delete(`/todos/${id}`);
  };

  //수정 함수
  const onClickEditButtonHandler = async () => {
    api.patch(`/todos/${targetId}`);
  };

api를 import 하고 기존에 axios.을 사용하였던 것을 api.으로 바꾸고,

주소도 지워줄 수 있어  /todos로 연결할 수 있다.

 

request, response에 적용해보기

요청을 보낼 때, 그리고 서버로부터 응답을 받을 때(혹은 실패할 때)

특정한 일을 수행해야 한다면 interceptors를 사용할 수 있다.

//api.js

import axios from "axios";

const instance = axios.create({
  baseURL: "http://localhost:4000",
  // timeout: 1,
  //0.001초의 시간을 주어 이 시간내에 응답을 못 받으면 오류가 나게 한다.
});

instance.interceptors.request.use(
  // 요청을 보내기 전 수행되는 함수
  function (config) {
    console.log("인터셉트 요청 성공!");
    return config;
  },
  // 오류 요청을 보내기 전 수행되는 함수
  function (error) {
    console.log("인터셉트 요청 오류!");
    return Promise.reject(error);
  }
);

instance.interceptors.response.use(
  //응답을 내보내기 전 수행되는 함수
  function (response) {
    console.log("인터넵트 응답 받았어요!");
    return response;
  },
  // 오류응답을 내보내기 전 수행되는 함수
  function (error) {
    console.log("인터셉트 응답 못받았어요...ㅠㅠ");
    return Promise.reject(error);
  }
);

export default instance;

이렇게 instance.interceptors를 사용하여 성공 시와 오류가 났을 때의 함수를 만들어준다.

또한, request와 response로 요청 시와 응답 시에 실행되도록 한다.

만약 settimeout:1을 해주게 되면 0.001초라는 짧은 시간내에 응답을 받아야 하기에 오류가 날 수 밖에 없다.

이런 식으로 오류가 났을 시의 함수를 확인하면 될 것 같다!