# TIL - 2026.08.07

ssls·2026년 8월 7일

📌 오늘 학습한 주제

  • REST API 기반 CRUD 비동기 통신 (Axios GET, POST, DELETE) 구현
  • React useMemo 기반의 메모이제이션을 활용한 필터링 성능 최적화 (BlogIndexPage.jsx)
  • React Router 기반의 1:N 관계 임베드 데이터 파싱 및 실시간 댓글 CRUD 처리 (BlogReadPage.jsx)
  • Styled Components 유틸리티 Prop($active) 기반의 카테고리 칩 UI 및 상태 관리 (BlogWritePage.jsx)

💡 오늘 배운 것 — 나만의 언어로

① 오늘의 목표

  • Styled Components와 Axios 비동기 통신을 결합하여 게시글 목록 조회, 카테고리 필터링, 게시글 작성 및 댓글 작성/삭제(CRUD)까지 완벽하게 연동되는 종합 블로그 서비스 구축하기

② 학습할 내용

  • 메모이제이션 기반 목록 필터링 (BlogIndexPage.jsx): useState로 선택된 카테고리를 관리하고, useMemo Hook을 적용하여 블로그 데이터 목록(blogs)이나 선택된 카테고리(selectedCategory)가 변경될 때만 필터링 연산을 재수행하도록 성능을 최적화했다.
  • 1:N 데이터 조인 및 댓글 CRUD (BlogReadPage.jsx): Axios로 GET /blogs/${id}?_embed=comments 요청을 보내 게시글 상세 정보와 연관된 댓글 배열을 한 번에 수신했다. 또한 POST /comments로 새 댓글을 추가할 때는 불변성을 유지하며 기존 상태 배열을 연장([...prev, newComment])하고, DELETE /comments/${id} 실행 시 filter()를 활용해 불필요한 전체 새로고침 없이 화면을 실시간 업데이트했다.
  • 카테고리 칩 선택 UI 및 게시글 작성 (BlogWritePage.jsx): Transient Prop($active)을 사용하여 선택된 카테고리 버튼의 스타일을 동적으로 전환하고, 입력 폼의 데이터를 POST /blogs로 전송하여 새 글 등록 후 메인 페이지로 이동시켰다.

💻 오늘 핵심 코드

1. useMemo를 활용한 카테고리 필터링 최적화 (BlogIndexPage.jsx)

import { useEffect, useMemo, useState } from "react";
import api from "../../../api/axios";
import BlogList from "../list/BlogList";

const BlogIndexPage = () => {
    const CATEGORIES = ["전체", "개발", "생활", "취미", "일상"];
    const [blogs, setBlogs] = useState([]);
    const [selectedCategory, setSelectedCategory] = useState("전체");

    // 블로그 데이터 로딩
    const loadData = async () => {
        await api.get(`/blogs`)
            .then(response => {
                if(response.status === 200) setBlogs(response.data);
            })
            .catch(error => console.log(`debug >>>> error`, error));
    }

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

    // useMemo를 적용해 불필요한 필터 재연산 방지
    const filteredBlogs = useMemo(() => {
        return selectedCategory === "전체"
            ? blogs
            : blogs.filter((blog) => blog.category === selectedCategory);
    }, [blogs, selectedCategory]);

    return (
        <Container>
            <CategoryRow>
                {CATEGORIES.map((category) => (
                    <CategoryChip
                        key={category}
                        $active={category === selectedCategory}
                        onClick={() => setSelectedCategory(category)}
                    >
                        {category}
                    </CategoryChip>
                ))}
            </CategoryRow>
            <BlogList ary={filteredBlogs || []} />
        </Container>
    );
}

2. 댓글 실시간 비동기 작성 및 삭제 처리 (BlogReadPage.jsx)

const BlogReadPage = () => {
    const { blogId } = useParams();
    const [comments, setComments] = useState([]);
    const [comment, setComment] = useState('');

    // 댓글 등록 핸들러 (POST & 불변성 유지 상태 업데이트)
    const commentHandler = async () => {
        await api.post('/comments', { blogId: Number(blogId), comment, email: user })
            .then(response => {
                if(response.status === 201) {
                    setComments(prev => [...prev, response.data]); // 실시간 UI 추가
                    setComment('');
                }
            });
    };

    // 댓글 삭제 핸들러 (DELETE & 필터링 상태 업데이트)
    const commentDeleteHandler = async (e, id) => {
        await api.delete(`/comments/${id}`)
            .then(response => {
                if(response.status === 200) {
                    setComments(comments.filter(c => c.id !== id)); // 실시간 UI 제거
                }
            });
    };

    return (
        <Container>
            <BlogCommentList comments={comments || []} handler={commentDeleteHandler} />
            <TextInput value={comment} handler={(e) => setComment(e.target.value)} />
            <Button title='댓글 작성' onClick={commentHandler} />
        </Container>
    );
}

🛠️ 실습 / 결과물 & 참고 자료

③ 실습 / 결과물

  • 강의 실습코드: BlogIndexPage.jsx, BlogReadPage.jsx, BlogWritePage.jsx, BlogCommentItem.jsx 연동을 통해 카테고리 필터링이 적용된 메인 페이지, 게시글 상세 및 댓글 CRUD 기능, 카테고리 선택 글 작성 화면을 구축함 [cite: 38, 40, 41, 42].

④ 참고 자료


🔍 문제와 해결

  • 막힌 부분: 댓글 등록/삭제 후 전체 페이지를 다시 불러오지 않고 부분 리렌더링을 일으킬 때 state 배열 조작 미숙으로 화면이 즉시 갱신되지 않는 현상 점검 [cite: 41].
  • 해결 방법: 댓글 추가 시 전개 연산자([...prev, newComment])를 활용해 기존 배열의 불변성을 유지하고, 삭제 시에는 filter(c => c.id !== id) 조건을 적용해 해당 아이템만 제외된 새 배열을 State로 설정하여 실시간 반응형 UI를 구현함 [cite: 41].

🎯 다음에 할 일

  • React useCallback Hook을 사용하여 하위 컴포넌트로 전달되는 핸들러 함수 재생성 방지하기
  • 서버 에러 발생 시 사용자에게 보여줄 공통 Toast/Alert UI 메시지 컴포넌트 추가해보기
profile
성장중

0개의 댓글