Day34 _HTTP, json-server, TanStack Query

정소현·2024년 9월 5일
0

스파르타

목록 보기
42/50

HTTP, json-server, TanStack Query

🌼 HTTP

HTTP의 개념

클라이언트-서버 모델 기반 동작(클라이언트가 요청 서버가 응답 반환)

HTTP의 특징

  • 무상태성
    : 모든 요청 독립, 이전 쵸엉의 정보를 기억하지 않음 이를 무상태라고 함
  • 확장성
  • 유연성
    : 다양한 데이터 형식 ex)텍스트, 이미지, 비디오

HTTP 메세지 구조

  • 요청 메세지
    - 요청 라인
    : 메서드(GET,POST,등), URL, HTTP 버전
    • 헤더
      : 요청의 추가 정보, 브라우저 정보, 인증 정보 등
    • 본문
      : 선택적 주로 POST 메서드에서 사용
    • 응답 메세지
      : 서버가 클라이언트의 요청에 대한 응답을 보낼 때 사용

HTTP 상태코드

: 서버가 클라이언트의 요청을 처리한 결과를 나타냄
상태코드는 세 자리 숫자로 구성, 첫 번째 자리에 따라 의미가 달라짐

1 : 1xx 정보

  • 100 Continue
    : 요청의 일부를 서버가 받았으며, 나머지를 계속 보내라는 의미

2 : 2xx 성공

  • 200 OK
    : 요청이 성공적으로 처리
  • 201 Created
    : 요청이 성공, 새로운 자원이 생성되었음을 의미

3 : 3xx 성공

  • 301 Moved permanently
    : 요청한 리소스가 영구적으로 새로운 URL 로 이동했음을 나타냄
  • 302 Found
    요청한 리소스가 임시로 다른 URL로 이동했음을 나타냄

4. 4xx 클라이언트 오류

  • 400 Bad Request
    : 잘못된 요청
  • 401 Unauthorized
    : 인증이 필요함
  • 404 Not Found
    : 요청한 리소스를 찾을 수 없음을 나타냄

5. 5xx 서버오류

  • 500 Internal Server Error
    : 서버가 요청을 처리하는 동안 오류가 발생
  • 502 Bad Gat4eway
    : 서버가 게이트웨이 또는 프록시 역할을 하는 서버로부터 유효하지 않은 응답을 받음을 의미

HTTP 메서드 및 Rest API

HTTP 메서드를 클라이언트가 서버에게 요청의 성격을 알리는데 사용
REST API는 HTTP 메서드를 사용하여 CRUD 작업을 수행


HTTP METHOD / REST API

  1. GET
  • 서버로부터 데이터를 요청할 때 사용, 요청 데이터가 URL에포함되어 전송 주로 데이터를 조회할 때 사용
  • REST API에서의 사용 : 특정 리소스를 조회할 때 사용
  1. POST
  • 서버에 데이터를 제출할 때 사용
  • 요청 데이터가 요청 본문에 포함되어 전송됨. 주로 데이터를 생성하거나 제출할 때 사용
  • REST API에서의 사용 : 새로운 리소스를 생성할 때 사용
  1. PUT, PATCH
  • 서버의 데이터를 업데이트 할 때 사용
  • 요청 본문에 포함되어 전송 주로 기존 데이터를 수정할 때 사용
  1. DELETE
  • 서버의 데이터를 삭제할 때 사용

🌟 RESTful 원칙에 따라 작성된 API명세서

엔드포인트


🌼 json-server란?

root 경로에 db.json을 만들어줌

yarn add json-server

Root에 db.json 생성 후 초기 값 작성

{
  "todos": [
    {
      "id": 1,
      "title": "json-server",
      "content": "json-server를 배워봅시다."
    }
  ],
  "users": []
}
yarn json-server db.json --port 4000

구동연결시켜줌

yarn json-server db.json --port 4000

간단한 DB완성


🌼 Axios

Axios란?
: node.js와 브라우저를 위한 Promise 기반 http 클라이언트
http를 이용해서 서버와 통신하기 위해 사용하는 패키지

fetch처럼 데이터를 불러올 수 있다.
fetch처럼 한번 더 데이터를 변환할 필요가 없음.

yarn add axios

1. Axios.get

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

axios.get(url[,config])
// src/App.js

import React, { useEffect, useState } from "react";
import axios from "axios"; // axios import 합니다.

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

	// axios를 통해서 get 요청을 하는 함수를 생성합니다.
	// 비동기처리를 해야하므로 async/await 구문을 통해서 처리합니다.
  const fetchTodos = async () => {
    const { data } = await axios.get("http://localhost:4000/todos");
    setTodos(data); // 서버로부터 fetching한 데이터를 useState의 state로 set 합니다.
  };
	
	// 생성한 함수를 컴포넌트가 mount된 후 실행하기 위해 useEffect를 사용합니다.
  useEffect(() => {
		// effect 구문에 생성한 함수를 넣어 실행합니다.
    fetchTodos();
  }, []);

	// data fetching이 정상적으로 되었는지 콘솔을 통해 확인합니다.
  console.log(todos);
  return <div>App</div>;
};

export default App;

1. Axios.post

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

보통 서버에 데이터를 추가할 때 사용

// src/App.jsx

import React, { useEffect, useState } from "react";
import axios from "axios"; // axios import 합니다.

const App = () => {
  // 새롭게 생성하는 todo를 관리하는 state
  const [todo, setTodo] = useState({
    title: "",
  });

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

  const fetchTodos = async () => {
    const { data } = await axios.get("http://localhost:4000/todos");
    setTodos(data);
  };

	// (원리를 꼭 이해합시다!)
	// HTTP에서는 body에 javascript 객체를 direct로 넣을 수 없어요!
	// axios는 내부적으로 JSON.stringify를 적용하기 때문에 이처럼 편리하게 사용하는 것 뿐입니다.
  const onSubmitHandler = async(todo) => {
    await axios.post("http://localhost:4000/todos", todo);
  };
  
  // 만일 fetch를 사용했다면, 이렇게 JSON.stringify를 '직접' 해주어야 해요.
  // await fetch("http://localhost:4000/todos", {
  //   method: "POST",
  //   headers: {
  //     "Content-Type": "application/json",
  //   },
  //   body: JSON.stringify(todo),
  // });

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

  return (
    <>
      <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.title}</div>
        ))}
      </div>
    </>
  );
};

export default App;

3. Axios.delete

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

axios.delete(url[,config])
// src/App.jsx

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

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

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

  const fetchTodos = async () => {
    const { data } = await axios.get("http://localhost:4000/todos");
    setTodos(data); 
  };

  const onSubmitHandler = (todo) => {
    axios.post("http://localhost:4000/todos", todo);
  };

	// 새롭게 추가한 삭제 버튼 이벤트 핸들러 
  const onClickDeleteButtonHandler = (todoId) => {
    axios.delete(`http://localhost:4000/todos/${todoId}`);
  };

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

  return (
    <>
      <form
        onSubmit={(e) => {
          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.title}
            <button
              type="button"
              onClick={() => onClickDeleteButtonHandler(todo.id)}
            >
              삭제하기
            </button>
          </div>
        ))}
      </div>
    </>
  );
};

export default App;

3. Axios.patch

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

axios.patch(url[,data[,config]])
// src/App.jsx

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

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

  // patch에서 사용할 id, 수정값의 state를 추가
  const [targetId, setTargetId] = useState(null);
  const [editTodo, setEditTodo] = useState({
    title: "",
  });

  const fetchTodos = async () => {
    const { data } = await axios.get("http://localhost:4000/todos");
    setTodos(data);
  };

  const onSubmitHandler = (todo) => {
    axios.post("http://localhost:4000/todos", todo);
  };

  const onClickDeleteButtonHandler = (todoId) => {
    axios.delete(`http://localhost:4000/todos/${todoId}`);
  };

  // 수정버튼 이벤트 핸들러 추가 👇
  const onClickEditButtonHandler = (todoId, edit) => {
    axios.patch(`http://localhost:4000/todos/${todoId}`, edit);
  };

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

  return (
    <>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          onSubmitHandler(todo);
        }}
      >
        {/* 👇 수정기능에 필요한 id, 수정값 input2개와 수정하기 버튼을 추가 */}
        <div>
          <input
            type="text"
            placeholder="수정하고싶은 Todo ID"
            onChange={(ev) => {
              setTargetId(ev.target.value);
            }}
          />
          <input
            type="text"
            placeholder="수정값 입력"
            onChange={(ev) => {
              setEditTodo({
                ...editTodo,
                title: ev.target.value,
              });
            }}
          />
          <button
						// type='button' 을 추가해야 form의 영향에서 벗어남
            type="button"
            onClick={() => onClickEditButtonHandler(targetId, editTodo)}
          >
            수정하기
          </button>
        </div>
        <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의 아이디를 화면에 표시 */}
            {todo.id} :{todo.title}
            <button
              type="button"
              onClick={() => onClickDeleteButtonHandler(todo.id)}
            >
              삭제하기
            </button>
          </div>
        ))}
      </div>
    </>
  );
};

export default App;

💥 Axios와 Fetch 차이

fetch와 axios 모두 HTTP요청을 처리하기 위한 JavaScript 라이브러리이다.

차이점

  1. 기본 설정 및 인터셉터 지원
  • 기본설정 : axios는 기본설정을 정의하고 이를 통해 모든 요청에 공통 설정을 적용할 수 있다.

  • 인터셉터 : 요청 또는 응답을 가로채서 전처리 또는 후처리 할 수 있다.
    이를 통해 인증 토큰을 자동으로 추가하거나 오류를 일괄 처리할 수 있다.

  1. 더 나은 오류 처리
  • 에러 핸들링 : axios는 HTTP 상태 코드를 기준으로 한 일관된 오류 처리를 제공한다. fetch는 기본적으로 네트워크 오류만 catch블록으로 전달되고, 4xx, 5xx오류는 then 블록에서 처리해야 한다.
  1. 브라우저 호환성
  • 구형 브라우저 지원 : axios는 구형 브라우저와의 호환성이 좋다. fetch는 구형 브라우저에서 지원되지 않을 수 있어 폴리필이 필요
  • 간단한 사용법

Axios Custom instatnce의 개념과 필요성

Interceptor는 두 상황에서 흐름을 가로챔

  1. 요청(request)이 전송되기 전 (또는 요청을 보내기 전, 또는 요청이 출발하기 전
  2. 응답(response)의 then(성공)또는 catch(실패)가 처리되기 전

Redux thunk

Redux 미들웨어
action객체가 아닌 thunk함수를 dispatch 해줌


🌟 TanStack Query

: 서버상태관리 도구

서버상태 vs 클라이언트 상태?

클라이언트 상태는 UI 관련된 일시적인 데이터(ex: 폼 입력값 )을 의미
서버 상태는 서버에서 가져오는 데이터를 포함하며, 캐싱, 동기화, 재검증 등의 복잡한 관리가 필요
(외부에서 가져오는 데이터를 어떻게 갱신하고 관리할 지 )

🌼주요 기능(GET, MODIFY, REFRESH)

  • 데이터 캐싱 : 동일한 데이터를 여러 번 요청하지 않도록 캐싱하여 성능을 유지시킴
  • 자동 리페칭 : 데이터가 변경되었을 때 자동으로 리페칭하여 최신 상태를 유지
  • 쿼리 무효화 : 특정 이벤트가 발생했을 때 쿼리를 무효화하고 데이터를 다시 가져올수 있음

get - useQuery
modify - useMutaion
refresh - InvalidateQuery

☘️ useQuery

useQuery는 데이터를 가져오기 위해 사용되는 TanStack Query의 대표적인 훅. 쿼리 키와 비동기 함수(패칭 함수)를 인자로 받아 데이터를 가져오고, 로딩 상태, 오류 상태, 그리고 데이터를 반환

yarn add @tanstack/react-query

App.jsx나 main.jsx에서

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </StrictMode>
);

☘️ useMutation

  • useMutation은 데이터를 생성, 수정, 삭제하는 등의 작업에 사용되는 훅 CUD에 대한 비동기 작업을 쉽게 수행하고, 성공 또는 실패 시에 추가적인 작업을 실행할 수 있기 때문에 useQuery와 함께 가장 대표적인 TanStack Query hook이라고 할 수 있음
  • 비동기 작업을 쉽게 처리한다는 말 안에는 작업이 완료된 후에 관련된 쿼리를 무효화하는 과정이 포함되는데 이 역시도 TanStack Query의 핵심 개념임 . 최신 데이터를 유지하는 데에 필수적인 요소
import { useMutation, useQuery } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";

const App = () => {
  const [todoItem, setTodoItem] = useState("");

  const fetchTodos = async () => {
    const response = await axios.get("http://localhost:4000/todos");
    return response.data;
  };

  const addTodo = async (newTodo) => {
    await axios.post("http://localhost:4000/todos", newTodo);
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  const { mutate } = useMutation({
    mutationFn: addTodo,
  });

  if (isPending) {
    return <div>로딩중입니다...</div>;
  }

  if (isError) {
    return <div>데이터 조회 중 오류가 발생했습니다.</div>;
  }

  return (
    <div>
      <h3>TanStack Query</h3>
      <form
        onSubmit={(e) => {
          e.preventDefault();

          const newTodoObj = { title: todoItem, isDone: false };

          // useMutation 로직 필요
          mutate(newTodoObj);
        }}
      >
        <input
          type="text"
          value={todoItem}
          onChange={(e) => setTodoItem(e.target.value)}
        />
        <button>추가</button>
      </form>
      <ul>
        {todos.map((todo) => {
          return (
            <li
              key={todo.id}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                backgroundColor: "aliceblue",
              }}
            >
              <h4>{todo.title}</h4>
              <p>{todo.isDone ? "Done" : "Not Done"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default App;

invalidateQueries

: invalidateQueries는 특정 쿼리를 무효화하여 데이터를 다시 패칭하게 하는 함수. 주로 useMutation과 함께 사용하여 데이터가 변경된 후 관련 쿼리를 다시 가져오도록 함
: 이를 통해 데이터가 항상 최신 상태로 유지될 수 있도록 도와줌. 예를 들어, 새로운 할 일을 추가한 후 기존의 할 일 목록을 다시 가져오도록 할 수 있음

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { useState } from "react";

const App = () => {
  const queryClient = useQueryClient();

  const [todoItem, setTodoItem] = useState("");

  const fetchTodos = async () => {
    const response = await axios.get("http://localhost:4000/todos");
    return response.data;
  };

  const addTodo = async (newTodo) => {
    await axios.post("http://localhost:4000/todos", newTodo);
  };

  const {
    data: todos,
    isPending,
    isError,
  } = useQuery({
    queryKey: ["todos"],
    queryFn: fetchTodos,
  });

  const { mutate } = useMutation({
    mutationFn: addTodo,
    onSuccess: () => {
      // alert("데이터 삽입이 성공했습니다.");
      queryClient.invalidateQueries(["todos"]);
    },
  });

  if (isPending) {
    return <div>로딩중입니다...</div>;
  }

  if (isError) {
    return <div>데이터 조회 중 오류가 발생했습니다.</div>;
  }

  return (
    <div>
      <h3>TanStack Query</h3>
      <form
        onSubmit={(e) => {
          e.preventDefault();

          const newTodoObj = { title: todoItem, isDone: false };

          // useMutation 로직 필요
          mutate(newTodoObj);
        }}
      >
        <input
          type="text"
          value={todoItem}
          onChange={(e) => setTodoItem(e.target.value)}
        />
        <button>추가</button>
      </form>
      <ul>
        {todos.map((todo) => {
          return (
            <li
              key={todo.id}
              style={{
                display: "flex",
                alignItems: "center",
                gap: "10px",
                backgroundColor: "aliceblue",
              }}
            >
              <h4>{todo.title}</h4>
              <p>{todo.isDone ? "Done" : "Not Done"}</p>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

export default App;

SWR

일단은 원래의 데이터를 쓰고 새로운 데이터가 오면 갈아끼워줌

캐시데이터의 보관

QueryClientProvider를 사용하여 React 애플리케이션 전체에서 캐시 데이터에 접근
내부적으로 React Context API를 사용한다.

  • cache context, cache data

TanStack Query의 생명주기

staleTime === 0
얼마나 신선한 상태를 유지할 것인가!

데이터가 캐시되고, 사용되고, 갱신되는 과정

default config(기본 설정)
staleTime : 0
refetchOnMount : true
refetchOnWindowFocus:true
refetchOnRecconnect : true
geTime(cacheTime) : 5분
retry : 3



NETWORK탭

  1. headers
  • Request URL을 통해서 우리가 의도한 URL로 post요청을 보냈음을 알 수 있음
  • Request Method를 통해서 POST메서드를 사용했음을 알 수 있다.
  • Status Codefmf xhdgotj 201코드를 받았고, 정상적으로 네트워크가 이루어졌음을 알 수 있음.
  • payload에서 우리가 보낸 body(값)를 알 수 있음

0개의 댓글