React

React 쇼핑몰 구조 이해하기 5편 - 게시판 CRUD 구조 분석


드디어 마지막 편이에요! 🎉

쇼핑몰에는 고객 문의 게시판이 있죠. 이번 편에서는 글 작성, 읽기, 수정, 삭제 기능이 어떻게 구현되어 있는지 분석해볼게요. 이걸 CRUD라고 해요.

이번 편에서 알아볼 내용

  • ✅ CRUD 개념 이해
  • ✅ App.js에서 게시판 상태 관리하는 방법
  • ✅ BoardList - 게시글 목록 구조
  • ✅ BoardWrite - 새 글 작성 흐름
  • ✅ BoardDetail - 글 상세 보기 + 조회수
  • ✅ BoardEdit - 글 수정 구조

CRUD란?

graph LR
    C["Create<br/>생성"] --> R["Read<br/>읽기"]
    R --> U["Update<br/>수정"]
    U --> D["Delete<br/>삭제"]
    
    style C fill:#e1ffe1
    style R fill:#e1f5ff
    style U fill:#fff4e1
    style D fill:#ffe1e1
기능설명컴포넌트
Create새 글 작성BoardWrite
Read글 목록/상세 보기BoardList, BoardDetail
Update글 수정BoardEdit
Delete글 삭제BoardList, BoardDetail

전체 데이터 흐름

graph TD
    A["App.js<br/>posts 상태 + 핸들러 함수들"]
    
    A --> B["BoardList<br/>posts, onDelete, onView"]
    A --> C["BoardWrite<br/>onAdd"]
    A --> D["BoardDetail<br/>posts, onDelete, onView"]
    A --> E["BoardEdit<br/>posts, onUpdate"]
    
    style A fill:#e1d5ff
    style B fill:#e1f5ff
    style C fill:#e1ffe1
    style D fill:#fff4e1
    style E fill:#ffe1e1

[!NOTE] 모든 게시판 데이터는 App.js에서 관리해요! 이걸 상태 끌어올리기(Lifting State Up) 패턴이라고 해요.

App.js - 게시판 상태 관리

상태 정의

const [posts, setPosts] = useState([
  {
    id: 1,
    title: "사과는 언제 배송이 되나요?",
    content: "어제부터 기다렸는데 아직 배송이 안됐어요.",
    author: "김과일",
    date: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).getTime(),
    views: 1,
  },
  // ...
]);

const [postId, setPostId] = useState(6);  // 다음 글 번호

핸들러 함수들

// 1. 새 글 추가
const handleAddPost = (newPost) => {
  const post = {
    id: postId,
    ...newPost,
    date: new Date().getTime(),
    views: 0,
  };
  setPosts([post, ...posts]);  // 맨 앞에 추가
  setPostId(postId + 1);
};

// 2. 조회수 증가
const handleViewPost = (id) => {
  setPosts(
    posts.map((post) =>
      post.id === id ? { ...post, views: (post.views || 0) + 1 } : post
    )
  );
};

// 3. 글 수정
const handleUpdatePost = (id, updatedPost) => {
  setPosts(
    posts.map((post) =>
      post.id === id
        ? { ...post, ...updatedPost, date: new Date().getTime() }
        : post
    )
  );
};

// 4. 글 삭제
const handleDeletePost = (id) => {
  setPosts(posts.filter((post) => post.id !== id));
};

BoardWrite - 새 글 작성 흐름

function BoardWrite({ onAdd }) {
  const navigate = useNavigate();
  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");
  const [author, setAuthor] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (!title || !content || !author) {
      alert("모든 항목을 입력해주세요.");
      return;
    }
    
    onAdd({ title, content, author });
    navigate("/board");
  };

  return (
    <Form onSubmit={handleSubmit}>
      {/* 폼 필드들 */}
      <Button type="submit">등록하기</Button>
    </Form>
  );
}

폼 제출 흐름:

sequenceDiagram
    participant U as 사용자
    participant BW as BoardWrite
    participant A as App.js
    participant BL as BoardList
    
    U->>BW: 폼 작성 후 등록 클릭
    BW->>BW: e.preventDefault()
    BW->>A: onAdd({title, content, author})
    A->>A: handleAddPost 실행
    A->>A: posts 상태 업데이트
    BW->>BL: navigate("/board")
    BL-->>U: 새 글이 추가된 목록 표시

BoardDetail - 글 상세 + 조회수 로직

function BoardDetail({ posts, onDelete, onView }) {
  const { id } = useParams();
  const post = posts.find((p) => p.id === Number(id));

  // 조회수 증가 (세션당 한 번만)
  useEffect(() => {
    if (!post || !onView) return;
    
    const viewedKey = `viewed_${id}`;
    const hasViewed = sessionStorage.getItem(viewedKey);
    
    if (!hasViewed) {
      sessionStorage.setItem(viewedKey, 'true');
      onView(Number(id));
    }
  }, [id]);

  if (!post) {
    return <div>게시글을 찾을 수 없습니다.</div>;
  }

  return (
    <Card>
      <h3>{post.title}</h3>
      <span>조회수: {post.views}</span>
      <div>{post.content}</div>
    </Card>
  );
}

조회수 로직:

graph TD
    A["페이지 접속"] --> B{"sessionStorage에<br/>viewed_id 있음?"}
    B -->|No| C["sessionStorage 저장"]
    C --> D["onView(id) 호출"]
    D --> E["조회수 +1"]
    B -->|Yes| F["아무것도 안 함"]
    
    style B fill:#fff4e1
    style E fill:#e1ffe1

[!TIP] sessionStorage를 사용하면 브라우저 탭을 닫기 전까지 조회수 중복 증가를 방지할 수 있어요!

BoardEdit - 글 수정 구조

function BoardEdit({ posts, onUpdate }) {
  const { id } = useParams();
  const post = posts.find((p) => p.id === Number(id));

  const [title, setTitle] = useState("");
  const [content, setContent] = useState("");

  // 기존 데이터로 폼 채우기
  useEffect(() => {
    if (post) {
      setTitle(post.title);
      setContent(post.content);
    }
  }, [post]);

  const handleSubmit = (e) => {
    e.preventDefault();
    onUpdate(Number(id), { title, content, author });
    navigate(`/board/${id}`);
  };

  return (
    <Form onSubmit={handleSubmit}>
      {/* 폼 필드들 - 기존값 표시 */}
      <Button type="submit">수정하기</Button>
    </Form>
  );
}

전체 시리즈 정리

5편에 걸쳐 React 쇼핑몰 구조를 분석해봤어요! 🎉

graph TD
    subgraph "1편: 기본 구조"
        A1["index.js → Provider → BrowserRouter → App.js"]
    end
    
    subgraph "2편: 상품 목록"
        B1["데이터 → map/filter/sort → Products"]
    end
    
    subgraph "3편: 상세 + 장바구니"
        C1["useParams → Detail → dispatch → Cart"]
    end
    
    subgraph "4편: Redux"
        D1["createSlice → configureStore → actions"]
    end
    
    subgraph "5편: 게시판"
        E1["상태 끌어올리기 → CRUD 핸들러 → Board 컴포넌트들"]
    end

핵심 개념 총정리:

개념설명
Provider, BrowserRouter앱 전체 설정1편
map, filter, sort배열 처리2편
useParamsURL 파라미터3편
dispatch, useSelectorRedux 사용법3,4편
createSlice상태+액션 정의4편
CRUD생성/읽기/수정/삭제5편
상태 끌어올리기부모에서 상태 관리5편

다음 단계

쇼핑몰 구조를 이해했다면 다음 단계로:

  1. 직접 코드 수정해보기 - 상품 추가, 기능 수정
  2. 백엔드 연결 - JSON Server나 Firebase 연동
  3. 스타일 커스터마이징 - 디자인 변경
  4. 기능 확장 - 로그인, 결제 등

수고하셨습니다! 이제 React 쇼핑몰의 전체 구조가 보이시죠? 🚀