🚀 React에서 공지사항(Notice) 페이징 최적화 & 성능 개선하기

2025. 2. 15. 18:17프론트엔드/REACT

728x90

🚀 React에서 공지사항(Notice) 페이징 최적화 & 성능 개선하기

공지사항을 구현할 때, 현재 공지사항 이후의 ID를 찾고, 다음 공지사항을 요청하는 방식을 사용했어요.
하지만 기존 방식대로 id+1, id+2 방식으로 가져오는 것은 문제가 발생할 수 있음 🤔

📌 문제점: id+1, id+2 방식의 한계

  1. 공지사항 ID가 연속되지 않을 가능성
    • id+1이 존재하지 않을 수도 있고, id+2가 건너뛸 수도 있음
    • 예를 들어, idArray = [6, 8, 10]이면 id+1(7)이 존재하지 않아서 요청이 실패함
  2. 불필요한 API 요청
    • 만약 마지막 공지사항이라면 id+1, id+2 요청 자체가 필요 없음
    • 하지만 기존 방식은 마지막인지 확인하지 않고 항상 2개 요청

해결 방법: idArray를 활용한 최적화

🔹 1. localStorage에 공지사항 ID 배열(idArray) 저장

  • 공지사항 목록을 가져올 때 모든 ID를 idArray로 저장
  • 예: idArray = [6, 8, 10, 15, 20]

🔹 2. idArray에서 현재 ID의 다음 ID 찾기

  • indexOf() 대신 Map을 사용해 빠르게 다음 ID 탐색 (O(1))
  • 마지막 ID라면 요청하지 않음

🔹 3. Promise.all()을 제거하고 요청 순차 처리

  • id+1 요청이 실패하면 id+2 요청도 무의미하므로 첫 번째 요청 성공 후 두 번째 요청 실행

🔥 최적화 코드

1️⃣ Answer.js: idArray 저장하기

import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axiosCookie from '../../axiosCookie';

export const Answer = () => {
  const navigate = useNavigate();
  const [notices, setNotices] = useState([]);

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

  // ✅ 공지사항 목록 가져와서 `idArray` 저장
  const fetchNotices = async () => {
    try {
      const response = await axiosCookie.get('/api/notice');
      if (response.data?.data?.noticeList) {
        const formattedNotices = response.data.data.noticeList.map(notice => ({
          ...notice,
          date: formatDate(notice.createdAt),
        }));

        setNotices(formattedNotices);

        // ✅ `idArray` 저장 (공지사항 ID만 저장)
        const idArray = formattedNotices.map(notice => notice.id);
        localStorage.setItem('noticeIds', JSON.stringify(idArray));
      }
    } catch (error) {
      console.error('❌ 공지사항 데이터를 불러오는 중 오류 발생:', error);
    }
  };

  const formatDate = (isoString) => {
    return new Date(isoString).toISOString().split('T')[0].replace(/-/g, '.');
  };

  return (
    <div>
      {notices.map(notice => (
        <div key={notice.id} onClick={() => navigate(`/notice/${notice.id}`)}>
          <span>{notice.title}</span>
          <span>{notice.date}</span>
        </div>
      ))}
    </div>
  );
};

2️⃣ NoticeDetail.js: idArray 기반으로 다음 ID 요청

import React, { useState, useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import axiosCookie from '../../axiosCookie';

const NoticeDetail = () => {
  const navigate = useNavigate();
  const { id } = useParams();
  const [notice, setNotice] = useState(null);
  const [nextNotices, setNextNotices] = useState([]);
  const [idArray, setIdArray] = useState([]);
  const [idMap, setIdMap] = useState(new Map());

  useEffect(() => {
    // ✅ `localStorage`에서 `idArray` 가져와서 `useState`에 저장
    const storedIds = JSON.parse(localStorage.getItem('noticeIds')) || [];
    setIdArray(storedIds);

    // ✅ `idMap`을 생성해 O(1) 탐색 가능하도록 변환
    const map = new Map(storedIds.map((id, index) => [id, index]));
    setIdMap(map);
  }, []);

  useEffect(() => {
    if (id && idArray.length > 0 && idMap.size > 0) {
      fetchNoticeById(Number(id));
    }
  }, [id, idArray, idMap]);

  // ✅ 현재 공지사항 가져오기 + 다음 공지사항 찾기
  const fetchNoticeById = async (noticeId) => {
    try {
      // ✅ 현재 공지사항 가져오기
      const currentResponse = await axiosCookie.get(`/api/notice/${noticeId}`);
      if (currentResponse.data?.data) {
        setNotice({
          title: currentResponse.data.data.title,
          date: formatDate(currentResponse.data.data.createdAt),
          content: currentResponse.data.data.content,
        });
      }

      // ✅ `idArray`에서 현재 ID의 다음 ID 찾기
      const currentIndex = idMap.get(noticeId);
      const nextNoticesData = [];

      if (idArray[currentIndex + 1]) {
        const nextResponse = await axiosCookie.get(`/api/notice/${idArray[currentIndex + 1]}`);
        nextNoticesData.push({
          id: nextResponse.data.data.id,
          title: nextResponse.data.data.title,
          date: formatDate(nextResponse.data.data.createdAt),
        });
      }

      if (idArray[currentIndex + 2]) {
        const nextResponse = await axiosCookie.get(`/api/notice/${idArray[currentIndex + 2]}`);
        nextNoticesData.push({
          id: nextResponse.data.data.id,
          title: nextResponse.data.data.title,
          date: formatDate(nextResponse.data.data.createdAt),
        });
      }

      setNextNotices(nextNoticesData);
    } catch (error) {
      console.error('❌ 공지사항 데이터를 불러오는 중 오류 발생:', error);
    }
  };

  const formatDate = (isoString) => {
    return new Date(isoString).toISOString().split('T')[0].replace(/-/g, '.');
  };

  return (
    <div>
      <h2>{notice?.title}</h2>
      <p>{notice?.date}</p>
      <p>{notice?.content}</p>

      <div>
        {nextNotices.length > 0 ? (
          nextNotices.map((item, index) => (
            <div key={index} onClick={() => navigate(`/notice/${item.id}`)}>
              <span>{item.title}</span>
              <span>{item.date}</span>
            </div>
          ))
        ) : (
          <p>다음 공지사항이 없습니다.</p>
        )}
      </div>
    </div>
  );
};

export default NoticeDetail;

🎯 최적화 정리

문제 기존 방식 (id+1, id+2) 최적화된 방식 (idArray 활용)

연속된 ID 문제 id+1이 없으면 요청 실패 idArray에서 정확한 다음 ID 찾음
불필요한 요청 항상 2개 요청 마지막이면 요청 안 함
탐색 속도 indexOf() (O(n)) Map 사용 (O(1))
API 요청 Promise.all() 동시 요청 첫 번째 성공 후 두 번째 요청

🚀 결론: 성능 최적화 성공!

  • ✅ id+1 방식 대신 idArray에서 정확한 다음 ID 찾기
  • ✅ localStorage 접근 최소화하여 렌더링 성능 개선
  • ✅ idMap을 사용해 탐색 속도를 O(1)으로 최적화
  • 불필요한 API 요청 제거네트워크 비용 절감

 

728x90