2024.12.18 (수) ~ 2024.12.20 (금)
- 추첨 기록을 밑에 로그 형식으로 남기고자 함
- 문제점
- DB를 사용하지 않으므로 로그를 남길 방안이 없음
- DB를 사용할 경우 이용자가 혹시나 자신의 당첨번호를 회사 관계자가 알 거라는 불안감을 심어줄 수 있음
- 이에 따라 DB를 사용하지 않는 방법으로 해결을 하고자 진행함
해결 방안
localStorage 사용
Local Storage는 웹 브라우저에서 제공하는 저장소로, 클라이언트 측에서 데이터를 영구적으로 저장할 수 있는 방법
주로 웹 애플리케이션에서 사용자의 데이터를 보관하는 데 사용됨.
- 특징
- 영구저장 : 로컬스토리지에 저장된 데이터는 브라우저를 닫거나 컴퓨터를 재부팅해도 사라지지 않음
- 도메인 기반 : 로컬스토리지는 도메인별로 저장됨, 즉 같은 도메인에서만 접근할 수 있으며, 다른 도메인에서는 해당 데이터에 접근할 수 없음
- 키-값 쌍 : 데이터는 문자열 형태로 키-값 쌍으로 저장됨, 각 데이터 항목은 고유한 키를 가지고 있으며, 이를 통해 검색하거나 수정할 수 있음
- 제약사항
- 용량제한 : 로컬스토리지 용량은 브라우저마다 다르지만, 일반적으로 5MB 정도로 제한되어 있음. 이는 쿠키보다 훨씬 더 많은 데이터를 저장할 수 있는 공간임 -> 또한 우리 데이터의 경우 로또 번호가 전부이기 때문에 5MB 데이터는 충분히 사용하고 남는 용량임
- 문자열 저장 : 로컬스토리지에 저장되는 데이터는 문자열 형태로만 저장됨. 객체와 배열 등 복잡한 구조는 JSON.stringify()를 사용하여 문자열로 변환한 후 저장해야하며, 다시 사용할 때는 JSON.parse()를 통해 원래 형태로 복원해야 함.
- 사용예시
- 사용자 설정 저장 (예, 테마, 언어 선택)
- 장바구니 정보 저장 (쇼핑 사이트에서)
- 최근 검색 기록 저장
- 로컬 스토리지는 사용하기 간편하고, 서버와의 통신 없이 클라이언트에서 데이터를 저장하고 관리할 수 있는 유용한 도구임 -> 그러나 민감한 정보는 보관하지 않는 것이 좋으며, 보안에 유의해야 함.
우리 코드의 경우 Result.tsx 파일을 생성하여 추첨 버튼 클릭 시 결과 값을 불러오고, 데이터는 localStorage로 이동하는 형식으로 방향을 정함
- 이슈 발생 : 웹에서 보여지는 로그 수를 10개로 정하고 그 이상 추첨 시 밀려나게 설정했으나 고정값으로 들어가는 문제가 있었음
import React, { useEffect, useState, useRef } from "react";
// SVG 파일 동적 임포트
const svgs = import.meta.glob("../assets/ball_*.svg", {
eager: true,
}) as Record<string, { default: string }>;
// 숫자를 기반으로 파일 경로를 반환하는 함수
const getImageByNumber = (num: number) => {
const filePath = `../assets/ball_${num}.svg`;
return svgs[filePath]?.default || null;
};
interface ResultProps {
log: number[][]; // 추첨 로그
onUpdateLog: (newLog: number[][]) => void; // 상위 컴포넌트에서 로그 업데이트를 관리
isDarkTheme: boolean; // Dark theme flag
}
function Result({ log, isDarkTheme }: ResultProps) {
const [localLogs, setLocalLogs] = useState<number[][]>([]);
const [delayedLogs, setDelayedLogs] = useState<number[][]>([]);
const [visibleLogs, setVisibleLogs] = useState<number[][]>([]);
const [page, setPage] = useState(1); // 페이지 번호 (5개씩 로드)
const loaderRef = useRef<HTMLDivElement | null>(null);
// 로그를 localStorage에서 불러옴
useEffect(() => {
const storedLogs = localStorage.getItem("logs");
if (storedLogs) {
setLocalLogs(JSON.parse(storedLogs));
}
}, []);
// 새 로그가 추가될 때 2.7초 딜레이 적용 (결과 미리보기 방지)
useEffect(() => {
if (log.length === 0) return;
const delay = setTimeout(() => {
setDelayedLogs((prevLogs) => {
const updatedLogs = [...prevLogs, ...log];
// 중복 제거
const uniqueLogs = updatedLogs
.map((item) => JSON.stringify(item)) // 문자열로 변환
.filter((value, index, self) => self.indexOf(value) === index) // 중복 제거
.map((item) => JSON.parse(item)); // 다시 객체로 변환
// localStorage에 저장
localStorage.setItem("logs", JSON.stringify(uniqueLogs));
return uniqueLogs;
});
}, 2700);
return () => clearTimeout(delay);
}, [log]);
// delayedLogs가 변경될 때, 초기 5개를 설정
useEffect(() => {
setVisibleLogs(delayedLogs.slice(0, 5));
}, [delayedLogs]);
// Intersection Observer 설정
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
const target = entries[0];
if (target.isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
},
{ threshold: 1.0 } // 완전히 보일 때 트리거
);
if (loaderRef.current) {
observer.observe(loaderRef.current);
}
return () => {
if (loaderRef.current) {
observer.unobserve(loaderRef.current);
}
};
}, []);
// 페이지 변경될 때마다 visibleLogs 업데이트
useEffect(() => {
const newLogs = delayedLogs.slice(0, page * 5);
setVisibleLogs(newLogs);
}, [page, delayedLogs]);
return (
<>
<p
className={`ml-[100px] mt-8 mb-6 text-[25px] font-bold ${
isDarkTheme ? "text-white" : "text-black"
}`}
>
추첨 기록
</p>
<div className="mt-4 mb-8 flex flex-col gap-4 w-[600px] h-[550px] overflow-y-auto rounded-lg bg-gray-100 shadow-lg">
{visibleLogs
.slice() // 원본 배열 복사
.reverse() // 역순 정렬 (최신 기록이 상단)
.map((draw, drawIndex) => (
<div
key={drawIndex}
className="flex flex-wrap gap-4 justify-center w-full p-4 rounded-lg bg-gray-300 shadow-md"
>
{draw.map((num, numIndex) => {
const imageSrc = getImageByNumber(num);
return imageSrc ? (
<img
key={numIndex}
src={imageSrc}
alt={`Ball ${num}`}
className="w-16 h-16"
/>
) : (
<div
key={numIndex}
className="w-16 h-16 flex items-center justify-center bg-gray-100 text-black rounded-full"
>
{num}
</div>
);
})}
</div>
))}
<div ref={loaderRef} className="h-4" /> {/* 무한 스크롤 트리거 */}
</div>
</>
);
}
export default Result;
'사이드프로젝트' 카테고리의 다른 글
| [Mori] 1일차 (0) | 2025.06.24 |
|---|---|
| [lottery] 백엔드 코드 배포 (Pythonanywhere 에러 및 이슈 해결 후 재 배포) (0) | 2024.12.23 |
| [lottery] 깃 허브 깃 용어 정리 (0) | 2024.12.17 |
| [lottery] 드롭다운 영역 특수 추첨 api 적용 (2) | 2024.12.17 |
| [lottery] 추첨치 추첨 애니메이션 추가 및 git 연동 (0) | 2024.12.17 |