Refactor toast duplicate prevention
Refactor the toast wrapper to improve the logic for preventing duplicate toast messages.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
|
||||
import * as React from "react"
|
||||
|
||||
const TOAST_LIMIT = 5 // 최대 5개로 제한
|
||||
@@ -134,6 +135,14 @@ let memoryState: State = { toasts: [] }
|
||||
let lastAction: { type: string; id?: string; time: number } | null = null
|
||||
|
||||
function dispatch(action: Action) {
|
||||
// 마지막 액션 정보 추출
|
||||
let actionId: string | undefined = undefined;
|
||||
if ('toast' in action && action.toast) {
|
||||
actionId = action.toast.id;
|
||||
} else if ('toastId' in action) {
|
||||
actionId = action.toastId;
|
||||
}
|
||||
|
||||
// 동일한 토스트에 대한 중복 액션 방지
|
||||
const now = Date.now();
|
||||
const isSameAction = lastAction &&
|
||||
@@ -141,7 +150,7 @@ function dispatch(action: Action) {
|
||||
((action.type === actionTypes.ADD_TOAST &&
|
||||
lastAction.time > now - 1000) || // ADD 액션은 1초 내 중복 방지
|
||||
(action.type !== actionTypes.ADD_TOAST &&
|
||||
action.toast?.id === lastAction.id &&
|
||||
actionId === lastAction.id &&
|
||||
lastAction.time > now - 300)); // 다른 액션은 300ms 내 중복 방지
|
||||
|
||||
if (isSameAction) {
|
||||
@@ -152,7 +161,7 @@ function dispatch(action: Action) {
|
||||
// 액션 추적 업데이트
|
||||
lastAction = {
|
||||
type: action.type,
|
||||
id: action.toast?.id,
|
||||
id: actionId,
|
||||
time: now
|
||||
};
|
||||
|
||||
|
||||
@@ -1,52 +1,139 @@
|
||||
|
||||
import { useToast as useOriginalToast, toast as originalToast, ToasterToast } from '@/hooks/use-toast';
|
||||
|
||||
// 토스트 이벤트 추적을 위한 히스토리 및 설정
|
||||
let toastHistory: { message: string; timestamp: number }[] = [];
|
||||
const DEBOUNCE_TIME = 1000; // 1초 내에 동일 메시지 방지
|
||||
const HISTORY_LIMIT = 10; // 히스토리에 유지할 최대 토스트 개수
|
||||
const CLEAR_INTERVAL = 30000; // 30초마다 오래된 히스토리 정리
|
||||
|
||||
// 히스토리 정리 함수
|
||||
const cleanupHistory = () => {
|
||||
const now = Date.now();
|
||||
toastHistory = toastHistory.filter(item => (now - item.timestamp) < 10000); // 10초 이상 지난 항목 제거
|
||||
/**
|
||||
* 토스트 중복 방지를 위한 설정값
|
||||
*/
|
||||
const TOAST_CONFIG = {
|
||||
DEFAULT_DURATION: 3000, // 기본 토스트 표시 시간 (ms)
|
||||
DEBOUNCE_TIME: 1500, // 동일 메시지 무시 시간 (ms)
|
||||
HISTORY_LIMIT: 10, // 히스토리에 저장할 최대 토스트 수
|
||||
CLEANUP_INTERVAL: 30000, // 히스토리 정리 주기 (ms)
|
||||
HISTORY_RETENTION: 10000 // 히스토리 보관 기간 (ms)
|
||||
};
|
||||
|
||||
// 주기적 히스토리 정리
|
||||
setInterval(cleanupHistory, CLEAR_INTERVAL);
|
||||
/**
|
||||
* 토스트 메시지 히스토리 인터페이스
|
||||
*/
|
||||
interface ToastHistoryItem {
|
||||
message: string; // 메시지 내용 (title + description)
|
||||
timestamp: number; // 생성 시간
|
||||
variant?: string; // 토스트 종류 (default/destructive)
|
||||
}
|
||||
|
||||
// 중복 토스트 방지 래퍼 함수
|
||||
const debouncedToast = (params: Omit<ToasterToast, "id">) => {
|
||||
const currentMessage = params.description?.toString() || params.title?.toString() || '';
|
||||
/**
|
||||
* 토스트 히스토리 관리 클래스
|
||||
*/
|
||||
class ToastHistoryManager {
|
||||
private history: ToastHistoryItem[] = [];
|
||||
private cleanupInterval: ReturnType<typeof setInterval>;
|
||||
|
||||
constructor() {
|
||||
// 주기적으로 오래된 히스토리 정리
|
||||
this.cleanupInterval = setInterval(
|
||||
() => this.cleanup(),
|
||||
TOAST_CONFIG.CLEANUP_INTERVAL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 새 토스트를 히스토리에 추가
|
||||
*/
|
||||
add(message: string, variant?: string): void {
|
||||
this.history.push({
|
||||
message,
|
||||
timestamp: Date.now(),
|
||||
variant
|
||||
});
|
||||
|
||||
// 히스토리 크기 제한
|
||||
if (this.history.length > TOAST_CONFIG.HISTORY_LIMIT) {
|
||||
this.history.shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 오래된 히스토리 정리
|
||||
*/
|
||||
cleanup(): void {
|
||||
const now = Date.now();
|
||||
this.history = this.history.filter(
|
||||
item => (now - item.timestamp) < TOAST_CONFIG.HISTORY_RETENTION
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 최근에 동일한 토스트가 표시되었는지 확인
|
||||
*/
|
||||
isDuplicate(message: string, variant?: string): boolean {
|
||||
const now = Date.now();
|
||||
|
||||
// 유사한 메시지가 최근에 표시되었는지 확인
|
||||
const isDuplicate = toastHistory.some(item =>
|
||||
item.message === currentMessage && (now - item.timestamp) < DEBOUNCE_TIME
|
||||
return this.history.some(item =>
|
||||
item.message === message &&
|
||||
item.variant === variant &&
|
||||
(now - item.timestamp) < TOAST_CONFIG.DEBOUNCE_TIME
|
||||
);
|
||||
}
|
||||
|
||||
// 중복이면 무시
|
||||
if (isDuplicate) {
|
||||
console.log('중복 토스트 감지로 무시됨:', currentMessage);
|
||||
/**
|
||||
* 히스토리 초기화 (테스트용)
|
||||
*/
|
||||
clear(): void {
|
||||
this.history = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 정리 타이머 해제 (메모리 누수 방지)
|
||||
*/
|
||||
dispose(): void {
|
||||
clearInterval(this.cleanupInterval);
|
||||
}
|
||||
}
|
||||
|
||||
// 싱글톤 인스턴스 생성
|
||||
const toastHistory = new ToastHistoryManager();
|
||||
|
||||
/**
|
||||
* 메시지 내용 추출 (title + description)
|
||||
*/
|
||||
const extractMessage = (params: Omit<ToasterToast, "id">): string => {
|
||||
return [
|
||||
params.title?.toString() || '',
|
||||
params.description?.toString() || ''
|
||||
].filter(Boolean).join(' - ');
|
||||
};
|
||||
|
||||
/**
|
||||
* 중복 방지 토스트 표시 함수
|
||||
*/
|
||||
const debouncedToast = (params: Omit<ToasterToast, "id">) => {
|
||||
const message = extractMessage(params);
|
||||
|
||||
// 빈 메시지 무시
|
||||
if (!message.trim()) {
|
||||
console.warn('빈 토스트 메시지가 무시되었습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
// 중복 검사
|
||||
if (toastHistory.isDuplicate(message, params.variant)) {
|
||||
console.log('중복 토스트 감지로 무시됨:', message);
|
||||
return;
|
||||
}
|
||||
|
||||
// 히스토리에 추가
|
||||
toastHistory.push({ message: currentMessage, timestamp: now });
|
||||
|
||||
// 히스토리 크기 제한
|
||||
if (toastHistory.length > HISTORY_LIMIT) {
|
||||
toastHistory.shift(); // 가장 오래된 항목 제거
|
||||
}
|
||||
toastHistory.add(message, params.variant);
|
||||
|
||||
// 실제 토스트 표시
|
||||
originalToast({
|
||||
...params,
|
||||
duration: params.duration || 3000, // 기본 3초로 설정
|
||||
duration: params.duration || TOAST_CONFIG.DEFAULT_DURATION,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 토스트 훅 래퍼
|
||||
*/
|
||||
export const useToast = () => {
|
||||
const toast = useOriginalToast();
|
||||
return {
|
||||
@@ -55,4 +142,12 @@ export const useToast = () => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 토스트 함수 래퍼 (훅을 사용하지 않는 컨텍스트용)
|
||||
*/
|
||||
export const toast = debouncedToast;
|
||||
|
||||
// 메모리 누수 방지를 위한 정리 함수 (필요시 호출)
|
||||
export const disposeToastHistory = () => {
|
||||
toastHistory.dispose();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user