[TIL/크래프톤 정글] DAY 99

배재준·2025년 6월 16일

크래프톤 정글 - TIL

목록 보기
91/93
post-thumbnail

2025.06.16

TIL(TODAY I LEARN)


  • 오늘한 내용 : 게시글, 댓글 작성 및 회원가입·로그인 api 요청 구현

  • WEEK 14 : 실력 다지기 - 프론트엔드


서버와 통신 해보자!

API 명세 작성

프론트와 백엔드 연동을 위한 RESTful API 명세.

기본 URL: /api


게시글 (Post)

기능메서드엔드포인트요청 데이터 (body)응답 데이터
목록 조회GET/posts게시글 배열
단일 조회GET/posts/:id게시글 객체
작성POST/posts{ title, content, author }생성된 게시글
수정PUT/posts/:id{ title, content }수정된 게시글
삭제DELETE/posts/:id성공 메시지

댓글 (Comment)

기능메서드엔드포인트요청 데이터 (body)응답 데이터
댓글 추가POST/posts/:id/comments{ author, content }생성된 댓글
댓글 전체 조회GET/posts/:id/comments댓글 배열

인증 (Auth)

기능메서드엔드포인트요청 데이터 (body)응답 데이터
회원가입POST/signup{ username, password }사용자 정보 or 성공 메시지
로그인POST/login{ username, password }{ token }
로그아웃POST/logout성공 메시지

예시 요청/응답

Request

{
  "title": "새 글 제목",
  "content": "내용입니다",
  "author": "작성자"
}

Response

{
  "id": 3,
  "title": "새 글 제목",
  "content": "내용입니다",
  "author": "작성자",
  "date": "2025-06-16",
  "comments": []
}

axios를 통한 서버 통신

  • 더 편리하게 HTTP 요청을 보낼 수 있게 해주는 외부 라이브러리
  • fetch보다 문법이 간결하고 자동 JSON 파싱, 에러 처리, 확장성에서 우수함
설치 필요
-------
npm install axios

사용 예시
-------
import axios from "axios";

useEffect(() => {
  axios.get("/api/posts")
    .then((res) => setPosts(res.data))
    .catch((err) => console.error(err));
}, []);
항목fetchaxios
지원 범위브라우저 기본 내장별도 설치 필요 (npm i axios)
코드 간결성상대적으로 복잡훨씬 간결하고 직관적
자동 JSON 변환응답 .json() 직접 호출해야 함자동으로 .data에 담김
타임아웃 설정직접 구현 필요기본 지원 (timeout 옵션)
에러 처리try-catch 또는 res.ok 체크 필요catch로 직관적 처리
인터셉터 기능없음요청/응답 가로채기 가능 → 토큰 처리 등에 유용

api.js로 axios 요청 분리

장점설명
모듈화요청 코드가 한 곳에 정리되어 관리하기 쉬움
재사용성여러 컴포넌트에서 같은 API 호출 시 중복 제거
기본 설정 통합baseURL, 헤더, 인터셉터 등 공통 설정 가능
테스트 편리함API 모듈만 별도로 mocking 가능
// src/api/api.js
import axios from "axios";

const api = axios.create({
  baseURL: "/api", // 실제 요청은 /api/posts, /api/login 등으로 구성됨
  timeout: 5000,
  headers: {
    "Content-Type": "application/json",
  },
});

// 게시글 목록 가져오기
export const fetchPosts = () => api.get("/posts");

// 게시글 하나 가져오기
export const fetchPostById = (id) => api.get(`/posts/${id}`);

// 게시글 생성
export const createPost = (data) => api.post("/posts", data);

// 로그인
export const loginUser = (data) => api.post("/login", data);

// 회원가입
export const signupUser = (data) => api.post("/signup", data);

export default api;

App.jsx

 // 게시글 목록을 상태로 관리 (초기값은 빈 배열)
  const [posts, setPosts] = useState([]);

  // 컴포넌트가 처음 마운트될 때 게시글 목록을 서버에서 불러옴
  useEffect(() => {
    fetchPosts()
      .then((res) => setPosts(res.data))
      .catch((err) => console.error("불러오기 실패:", err));
  }, []);
  • useState: 컴포넌트 내 상태(State) 관리
    • 값이 변경되면 자동으로 컴포넌트가 재렌더링
  • useEffect: 부수 효과(side-effect) 처리
    • 컴포넌트가 마운트될 때 실행 (ex. 서버에서 데이터 fetch)
    • 특정 값이 변경될 때 실행 (ex. props나 state)

BoardList.jsx

  {/* 게시글 목록 출력 */}
  {Array.isArray(posts) && posts.length > 0 ? (
    posts.map((post) => (
      // 각 게시글을 PostCard 컴포넌트로 렌더링
      // key는 React가 각 요소를 구분하는 데 필요
      <PostCard key={post.id} post={post} />
    ))
  ) : (
    <p className="text-gray-500">게시글이 없습니다.</p>
  )}
  • posts == [res.data] 요소가 없을때 처리를 추가
    • Array.isArray(posts) 로 검증

PostDetail.jsx

  1. useParams()를 통해 URL에서 게시글 ID 추출

  2. 댓글 목록(commentList)은 useState로 상태 분리

  3. useEffect로 단일 게시글 조회(fetchPostById)

    
    function PostDetail({ posts }) {
      const { id } = useParams(); // URL에서 post ID 추출
      const [post, setPost] = useState(null); // 게시글 내용
      const [commentList, setCommentList] = useState([]);
    
      // 게시글 불러오기
      useEffect(() => {
        fetchPostById(id)
          .then((res) => {
            setPost(res.data);
            setCommentList(res.data.comments || []);
          })
          .catch((err) => {
            console.error("게시글 조회 실패:", err);
          });
      }, [id]);
    ...
    }
    • 현재 구조는 commentList가 내부에 있는 중첩 구조 → 개선 여지 존재
  4. 댓글 등록은 CommentBox에서 입력, postDetail에서 처리

      //댓글 등록 처리 함수
      const handleAddComment = async ({ author, content }) => {
        try {
          const res = await addCommentToPost(id, { author, content });
    
          // 서버에서 등록된 댓글 반환한다고 가정 (id 포함)
          const newComment = res.data;
    
          // 로컬 상태 업데이트
          setCommentList([...commentList, newComment]);
        } catch (err) {
          console.error("댓글 등록 실패:", err);
          alert("댓글 등록에 실패했습니다.");
        }
      };

async/await

  • 자바스크립트에서 비동기 코드를 더 읽기 쉽게 작성할 수 있게 해주는 문법
  • 비동기 작업은 빠르지만 순서 보장이 안 되기 때문에, 특정 작업이 끝난 뒤에 실행되어야 할 작업은 await을 써서 순서를 강제로 맞추는 것 즉, 비동기적으로 처리하되, 순서가 중요한 부분만 동기적으로 보이게 처리하는 도구
async function 함수이름() {
  const result = await 비동기함수();
  console.log(result);
}
  • async : 해당 함수가 항상 Promise를 반환함을 명시
  • await : Promise가 해결(resolve)될 때까지 기다림

동기(Synchronous) vs 비동기(Asynchronous)

구분동기 (Synchronous)비동기 (Asynchronous)
의미작업을 순서대로 하나씩 처리다음 작업을 기다리지 않고, 동시에 처리
예시A → B → C 순차적으로 실행A 시작 → B, C 실행 → A 끝나면 결과 처리
특징끝날 때까지 기다림기다리지 않고 바로 다음 작업 수행
단점느린 작업 하나가 전체를 블로킹전체 흐름이 더 유연하고 빠름

CreatePost.jsx

항목변경 내용
작성자 입력란제거됨
작성자 처리localStorage.getItem("username") 사용
서버 요청createPost({ title, content, author }) 구조 유지
유효성 검사로그인 여부도 확인 (!author)
  • handleSubmit() 함수 변경
const handleSubmit = async (e) => {
    e.preventDefault();

    const author = localStorage.getItem("username"); // 로그인된 사용자명

    // 유효성 검사
    if (!title.trim() || !content.trim())
      return alert("제목과 내용을 입력해주세요.");
    if (!author) return alert("로그인이 필요합니다.");

    try {
      const res = await createPost({
        title,
        content,
        author, // 작성자는 따로 입력받지 않음
        date: new Date().toLocaleDateString(),
      });

      const newPost = res.data;
      setPosts([...posts, newPost]); // 로컬 상태 업데이트
      navigate("/"); // 목록 페이지로 이동
    } catch (err) {
      console.error("게시글 등록 실패:", err);
      alert("게시글 등록에 실패했습니다.");
    }

Login.jsx

  const handleLogin = async (e) => {
    e.preventDefault();

    try {
      const res = await loginUser({ username, password });

      // 로그인 성공 시 사용자 이름 저장
      localStorage.setItem("username", res.data.username); // 또는 res.data.user 등 백엔드 응답에 따라 조정

      alert("로그인 성공!");
      navigate("/"); // 홈으로 이동
    } catch (err) {
      console.error("로그인 실패:", err);
      alert("아이디 또는 비밀번호가 일치하지 않습니다.");
    }
  };

Signup.jsx

  const handleLogin = async (e) => {
    e.preventDefault();

    try {
      const res = await loginUser({ username, password });

      // 로그인 성공 시 사용자 이름 저장
      localStorage.setItem("username", res.data.username); // 또는 res.data.user 등 백엔드 응답에 따라 조정

      alert("로그인 성공!");
      navigate("/"); // 홈으로 이동
    } catch (err) {
      console.error("로그인 실패:", err);
      alert("아이디 또는 비밀번호가 일치하지 않습니다.");
    }
  };

  • TODO) JWT 인증

0개의 댓글