📌 오늘 학습한 주제
- 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(); }, []);
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('');
const commentHandler = async () => {
await api.post('/comments', { blogId: Number(blogId), comment, email: user })
.then(response => {
if(response.status === 201) {
setComments(prev => [...prev, response.data]);
setComment('');
}
});
};
const commentDeleteHandler = async (e, id) => {
await api.delete(`/comments/${id}`)
.then(response => {
if(response.status === 200) {
setComments(comments.filter(c => c.id !== id));
}
});
};
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].
🎯 다음에 할 일