React

[React] useEffect를 사용하여 동기 / 비동기 통신 - axios RESTApi호출

밍띠이 2024. 4. 11. 19:03
반응형

todolist 앱 개발 멘토링 중

백엔드와 연결을 하는 와중에 axios로 RESTAPI 호출을 하며 정말 다양한 오류들을 만나고 있다.

그 중 가장 먼저 해결 해야 했던 부분이,

정말 간단하게 axios를 사용해서 값을 useState로 넣어주려 했는데, 비동기 통신으로 데이터를 받아와 버리니 웹이 뜨자마자 오류가 폭발했다.

자바스크립트는 비동기통신을 하기 때문에 요청을 보내고 기다리지 않는다.

그렇기 때문에 이를 동기화 하여 통신하게끔 처리해 주어야 하는데 그때 async, await이 사용된다.

동기 통신(Synchronous)은 요청을 완료 하길 기다렸다 결과값을 받아와서 사용할 수 있기 때문에 순차적으로 진행이 필요할때 사용하게 된다.

axios

하지만 axios는 promise 기반 비동기통신(Asynchronous)을 한다. promise는 결과값을 받게 되지만 데이터를 받아오지 않고 받기를 약속만 한 상태라고 생각하면 된다. 데이터를 받아서 결과값을 받으려면 동기통신이 필요하게 되는 이유다.

axios를 리엑트에서 사용하고자 할 때에는, 라이브러리를 다운받아 이용해야한다.

npm i axios

axios는 제공하는 기능 중 get을 이용하면 get 호출을 할 수 있다.

axios.get("엔드포인트주소");

헤더나 바디에 값을 담아 보내게 될 경우에는 주소 뒤에 바디 데이터, 헤더 전달을 하면 된다

axios.get("엔드포인트주소", {
        data: {
            // 들어갈 정보
        },
        headers: {
            // 들어갈 정보
        },
});

async / await

await을 사용하면 이러한 과정에서 데이터를 받은 후 결과값으로 처리하겠다는 의미가 된다.

await을 사용하려면 호출하려는 최상위 함수에 async를 명시해 주어야 한다.

const getListSync = async () => {
    const response = await axios.get("http://localhost:8080/todolist");
    console.log(response.data);
};

이렇게 하면 getListSync를 호출하였을 때, axios에서 get요청을 보낸 후 결과값을 기다렸다가 송신이 되면 response에 값이 저장된다.

axios는 JSON 형식으로 Response.data 안에 결과값을 받아오기 때문에 콘솔로 response.data를 확인 해 보면 원하는 리스트 값을 볼 수 있다.

만약 todolist의 작성 내용의 값이 이 리스트의 title이라는 값으로 들어오게 된다면 아래와 같이 첫번째 항목의 해당 값을 불러 올 수 있다.

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

const TodoListPage() {
    const [title, setTitle] = useState("");

    const getListSync = async () => {
        const response = await axios.get("http://localhost:8080/todolist");
        console.log(response.data);
        setTitle(response.data[0].title);
    };

    return (
        <>
        <div>{title}</div>
        </>
    );
}

useEffect

useEffect는

처음 컴포넌트가 마운트 되었을때,

서버가 시작되고 화면이 로딩될때,

특정 변수가 업데이트 되었을때 등

어떤 상황에 따라 값을 변경하거나, 함수를 실행시키기 위해 사용하는 훅이다.

useEffect(() => {
    // ... 필요 로직
});

이렇게 작성할 수 있는데, 괄호 뒤에는 의존성 배열로 뎁스를 정해줄 수 있다.

비워두게 된다면 컴포넌트가 리렌더링 될때마다 useEffect 내부의 로직이 실행되기 때문에

빈 배열([])을 사용하여 컴포넌트가 마운트 될 때 단 한번만 함수가 실행되도록 할 수 있다.

useEffect(() => {
    // ... 필요 로직
},[]);

이 방식을 이용해서 컴포넌트가 마운트 될때

RESTAPI를 axios로 호출하여 화면에 필요한 데이터들을 보여줄 수 있다.

useEffect(() => {
    const getListSync = async () => {
        const response = await axios.get("http://localhost:8080/todolist");
        // .. 추가 로직
    };
    getListSync();
},[]);

만약 배열로 값을 받아와서 각각의 항목을 보여 줄 경우에는 리턴문에서 map을 사용하여 값을 뿌려줄 수 있다.

투두 리스트를 디비에서 api로 호출하여 가져오는 예시 코드를 작성해 보자면,

// MemoComponent
import React, { useEffect, useState } from "react";
import MemoListComponent from "./MemoList/MemoListComponent"; // 메모항목을 보여 줄 컴포넌트
import axios from "axios"; // axios 임포트

function MemoComponent() {
  const [todoList, setTodoList] = useState([]); // 투두리스트를 담기위해 [] 배열로 초기화

  useEffect(() => {
    const getMemoList = async () => {
      const response = await axios.get("/todos"); // 요청 endpoint로 get 요청
      setTodoList(response.data); // 배열에 받아온 응답 데이터로 업데이트
    };
    getMemoList();
  }, []); // 마운트 될 때 한번 실행

  return (
    <div
      style={{
        width: "60%",
        textAlign: "center",
      }}
    >
      <div>
        <input
          type="text"
          placeholder="투두리스트를 작성하세요."
          style={{
            padding: "10px",
            margin: "10px",
            width: "50%",
          }}
        />
        <button type="button" style={{ padding: "10px", borderStyle: "none" }}>
          추가
        </button>
      </div>
      {todoList.map((todo) => ( // map함수 사용
        <MemoListComponent key={todo.id} data={todo} /> // 고유 key prop 추가(id)
      ))}
    </div>
  );
}

export default MemoComponent;

받아온 todolist 배열을 map으로 하나씩 개별 todo값으로 보내줄 수 있다.

아래는 받는 예시

// MemoListComponent
import React, { useState } from "react";

function MemoListComponent({ todo }) {
  const [isHovered, setIsHovered] = useState(false); // 마우스 호버 상태 관리

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "center",
        gap: "10px",
      }}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <input
        type="checkbox"
        name="checked"
        checked={todo.completed}
        onChange={() => {}} // 백단 post 요청 보내야함
        style={{
          appearance: "none",
          WebkitAppearance: "none",
          MozAppearance: "none",
          width: "1.5rem",
          height: "1.5rem",
          border: "1.5px solid #ddd",
          borderRadius: "50%",
          backgroundColor: todo.completed ? "blue" : "transparent",
          cursor: "pointer",
        }}
      />
      <div
        style={{
          backgroundColor: isHovered ? "#b3b3b3" : "#ececec", // 호버 상태에 따른 배경색 변경
          padding: "10px",
          width: "50%",
          cursor: "pointer", // 마우스 호버 효과 추가
        }}
      >
        {todo.title}
      </div>
    </div>
  );
}

export default MemoListComponent;

생성, 수정, 삭제, 조회(개별)도 아래와 같이 axios를 이용하여 가능하다.

axios.post(url[, data[, config]]) // 생성
axios.patch(url[, data[, config]]) // 수정
axios.delete(url[, config]) // 삭제
axios.get(url[, config]) // 조회
반응형