React를 활용한 공지사항 제작
공지사항 기본 파일구조

공지사항 파일 플로어 다이어그램

1. 기본 파일 생성 하기
App.tsx (라우터)
import { NoticeListPage } from '@/pages/notice';
import { NoticeWritePage } from '@/pages/notice';
import { NoticeDetailPage } from '@/pages/notice';
<Route path="/notice" element={<NoticeListPage />} />
<Route path="/notice/write" element={<NoticeWritePage />} />
<Route path="/notice/detail/:id" element={<NoticeDetailPage />} />
pages/notice/ui/notice-list-page.tsx
import React from 'react'
export const NoticeListPage = () => {
return (
<div>
<h1>공지사항 목록</h1>
</div>
);
};
pages/notice/ui/notice-detail-page.tsx
import { useParams } from 'react-router-dom';
export const NoticeDetailPage = () => {
const { id } = useParams(); // URL에서 id 꺼냄 (/notice/detail/1 → id = "1")
return (
<div>
<h1>{id}번 공지사항 상세</h1>
</div>
);
};
pages/notice/ui/notice-write-page.tsx
export const NoticeWritePage = () => {
return (
<div>
<h1>공지사항 작성</h1>
</div>
);
};
pages/notice/index.ts 인덱스 연결
export { NoticeListPage } from './ui/notice-list-page';
export { NoticeWritePage } from './ui/notice-write-page';
export { NoticeDetailPage } from './ui/notice-detail-page';
2. 공지사항 API 연결
features/notice/index.ts
export type {
NoticeCreateRequest,
NoticeDetail,
NoticeListItem,
PageResponse
} from './model/types';
export { NoticeList } from './ui/notice-list';
features/notice/model/types.ts
// 백엔드 응답/요청 타입 정의
export interface NoticeListItem { id: number; title: string; ... }
export interface NoticeDetail { id: number; title: string; content: string; ... }
export interface NoticeCreateRequest { title: string; content: string; ... }
export interface PageResponse<T> { items: T[]; totalElements: number; ... }
features/notice/api/notice-api.ts
import apiClient from '@/shared/api/api-client';
const BASE_URL = '/notices';
// apiClient.getData → 응답에서 data만 바로 반환
export const getNoticeList = async (page = 0, size = 20) =>
apiClient.getData(`${BASE_URL}/company/${companyId}`, { params: { page, size } });
export const getNoticeDetail = async (id: number) =>
apiClient.getData(`${BASE_URL}/${id}`);
export const createNotice = async (data: NoticeCreateRequest) =>
apiClient.postData(`${BASE_URL}`, data);
features/notice/model/use-notice-list.ts

import { useState } from 'react';
// useState : 컴포넌트 안에서 변하는 값(상태)을 관리하는 리액트 내장 훅
// → page 번호가 바뀔 때마다 화면이 다시 그려져야 하므로 필요
import { useNavigate } from 'react-router-dom';
// useNavigate : 코드로 페이지를 이동시키는 훅
// → 행 클릭 시 /notice/detail/1 로 이동할 때 필요
import { useQuery } from '@tanstack/react-query';
// useQuery : API 호출 + 로딩상태 + 캐싱을 자동으로 처리해주는 훅
// → getNoticeList 호출하고 결과를 data에 담아줌
import { getNoticeList } from '../api/notice-api';
// getNoticeList : 우리가 만든 API 호출 함수
// → useQuery 안에서 실제로 서버에 요청을 보내는 역할
export const useNoticeList = () => {
const navigate = useNavigate();
// navigate : 페이지 이동 함수 꺼내기
const [page, setPage] = useState(0);
// page : 현재 페이지 번호 (0부터 시작)
// setPage : page 값을 바꾸는 함수 → 페이지 클릭 시 사용
// useState(0) : 초기값 0
const [size] = useState(20);
// size : 한 페이지에 보여줄 행 수 (20으로 고정)
// setSize 없음 → 바꿀 일이 없으니 꺼내지 않음
//API 호출
const { data, isLoading } = useQuery({
//이 데이터의 고유 이름 (캐싱 키)
queryKey: ['notice', 'list', page, size], // page, size가 바뀌면 자동으로 API 재호출됨
//실제로 실행할 API 함수
queryFn: () => getNoticeList(page, size), // page, size를 넘겨서 해당 페이지 데이터를 가져옴
});
// id를 받아서 해당 공지사항 상세 페이지로 이동
const handleRowClick = (id: number) => {
navigate(`/notice/detail/${id}`); // 예: id=1 → /notice/detail/1
};
return {
navigate, // 페이지 이동 함수
isLoading, // API 로딩 중 여부 (true/false)
data: data?.items ?? [], // data?.items : API 응답에서 목록 배열만 꺼냄
totalElements: data?.totalElements ?? 0, // 전체 데이터 수 (페이지네이션에 필요)
page, // 현재 페이지 번호
size, // 페이지당 행 수
setPage, // 페이지 변경 함수 → ag-Grid 페이지 클릭 시 사용
handleRowClick, // 행 클릭 이벤트 핸들러
};
};
3. Features UI 공지사항 리스트 연결
features\notice\ui\notice-list.tsx
import { useMemo } from 'react';
// useMemo : columns 를 매번 새로 만들지 않고 캐싱
// 리렌더링 될 때마다 columns 재생성하는 불필요한 연산 방지
import { theme } from 'antd';
// theme.useToken() : AntD 디자인 토큰 접근
// 라이트/다크 모드 전환 시 색상 자동 대응
import type { ColumnsType } from 'antd/es/table';
// ColumnsType<T> : 테이블 컬럼 배열에 타입 지정
// T 에 NoticeListItem 넣으면 dataIndex 등에서 타입 자동완성 지원
import { useNoticeList } from '../model/use-notice-list';
// API 호출, 페이지네이션 상태, 클릭 이벤트 등
// 목록에 필요한 모든 값과 함수를 가져오는 커스텀 훅
import { Container, Header, LoadingFlex, NoticeSpinner, NoticeTable } from './notice-list.styles';
// 이 파일에서 직접 스타일 작성하지 않고 스타일 파일에서만 가져옴
import type { NoticeListItem } from '../model/types';
// 컬럼 타입 지정에 필요한 공지사항 목록 아이템 타입
export const NoticeList = () => {
// 컴포넌트 선언/훅 연결: 훅에서 필요한 값들만 꺼내서 사용
const {
isLoading, // API 호출 중 여부 (true: 로딩중, false: 완료)
data, // 공지사항 목록 배열
totalElements, // 전체 데이터 수 (페이지네이션 총 개수 표시용)
page, // 현재 페이지 번호 (0부터 시작)
size, // 페이지당 행 수
setPage, // 페이지 번호 변경 함수 (페이지 클릭 시 호출)
handleRowClick, // 행 클릭 시 상세 페이지로 이동하는 함수
} = useNoticeList();
// 컬럼정의
// 컴포넌트 최초 렌더링 시 한 번만 계산하고 이후 캐싱된 값 재사용
const columns = useMemo<ColumnsType<NoticeListItem>>(() => [
{ title: '제목', dataIndex: 'title', key: 'title', width: 160, },
{ title: '등록일', dataIndex: 'registrationDateTime', key: 'registrationDateTime', width: 160, },
{ title: '상태', dataIndex: 'isActive', key: 'isActive', width: 160, },
], []);
if(isLoading) return (
<FlexLoading>
{/* 데이터 오기 전 빈 테이블 노출 방지 */}
<NoticeSpinner />
</FlexLoading>
)
return (
<FlexContainer>
<FlexHeader>
<h2>공지사항</h2>
</FlexHeader>
<NoticeTable
columns={columns} // 훅에서 가져온 목록 배열을 테이블에 바인딩
dataSource={data}
rowKey="id"
onRow={(record) => ({
onClick: () => handleRowClick(record.id), // 행 클릭 시 해당 공지사항 id로 상세 페이지 이동
})}
pagination={{
current: page + 1, // AntD 페이지는 1부터 시작, 서버는 0부터 시작 → +1 보정
pageSize: size,
total: totalElements, // 전체 데이터 수 기반으로 페이지 수 자동 계산
onChange: (p) => setPage(p - 1), // AntD에서 받은 페이지 번호 -1 해서 서버 형식으로 변환
}}
/>
</FlexContainer>
);
};
\features\notice\ui\notice-list.styles.ts
import styled from 'styled-components';
import { Flex, Table, Spin } from 'antd';
import type { NoticeListItem } from '../model/types';
// 전체 페이지를 감싸는 최상위 컨테이너
// 세로 방향으로 Header, Table 순서로 배치
export const FlexContainer = styled(Flex)`
height: 100%;
flex-direction: column;
gap: 0;
`;
// 헤더 영역
// 왼쪽 제목, 오른쪽 버튼 등을 양쪽 끝으로 배치
export const FlexHeader = styled(Flex)`
padding: 1rem;
justify-content: space-between;
align-items: center;
`;
// API 호출 중일 때 스피너를 화면 정중앙에 띄우는 컨테이너
export const FlexLoading = styled(Flex)`
min-height: 100vh;
align-items: center;
justify-content: center;
`;
// 목록 로딩 중 표시할 스피너
// 1.5rem = Spin size="large" 와 동일한 크기
export const NoticeSpinner = styled(Spin)`
font-size: 1.5rem;
`;
export const NoticeTable = styled(Table<NoticeListItem>)`
padding: 0 1rem;
// Table<NoticeListItem> : dataSource, columns 에 NoticeListItem 타입 적용
.ant-table-row {
cursor: pointer;
}
// 행 클릭 가능함을 포인터 커서로 표시
`;
4. Page 연결
\pages\notice\ui\notice-list-page.tsx
import { NoticeList } from '@/features/notice';
export const NoticeListPage = () => {
return (
<NoticeList />
)
}
feature에서 제작한 공지사항 리스트 page에 연결
'React' 카테고리의 다른 글
| [React] 공지사항 게시판 만들기(3): detail 공지사항 상세페이지 조회 (0) | 2026.03.23 |
|---|---|
| [React] 공지사항 게시판 만들기(2): create 공지사항 작성 (0) | 2026.03.23 |
| [React] 프로젝트 생성방법 vite, CRA (0) | 2026.01.07 |
| [React] NextJS 새로운 페이지 생성 및 연결 방법 (0) | 2026.01.07 |
| [React] NextJS 새로운 프로젝트 생성하기 (0) | 2026.01.07 |