refactor: 메모리 누수 정리 및 타이머 관리 개선, 이벤트 리스너 제거 함수 추가

This commit is contained in:
“hyeonggkim”
2025-11-14 16:47:38 +09:00
parent ffa89ffbb6
commit ae7fb5fd60
10 changed files with 283 additions and 12 deletions

View File

@@ -19,6 +19,7 @@ const props = withDefaults(defineProps<Props>(), {
})
const videoRef = ref<HTMLVideoElement | null>(null)
let pauseTimeoutId: ReturnType<typeof setTimeout> | null = null
// autoplay prop 변경 시 재생/정지 제어
watch(
@@ -26,19 +27,36 @@ watch(
shouldPlay => {
if (!videoRef.value) return
// 이전 타이머 정리
if (pauseTimeoutId) {
clearTimeout(pauseTimeoutId)
pauseTimeoutId = null
}
if (shouldPlay) {
videoRef.value.play().catch(err => {
console.warn('Video play failed:', err)
})
} else {
setTimeout(() => {
videoRef.value.pause()
videoRef.value.currentTime = 0
pauseTimeoutId = setTimeout(() => {
if (videoRef.value) {
videoRef.value.pause()
videoRef.value.currentTime = 0
}
pauseTimeoutId = null
}, 200)
}
}
)
onBeforeUnmount(() => {
// 타이머 정리
if (pauseTimeoutId) {
clearTimeout(pauseTimeoutId)
pauseTimeoutId = null
}
})
// src 변경 시 비디오 다시 로드
watch(
() => props.src,

View File

@@ -26,6 +26,7 @@ const { addArrowClickListeners } = useSplideArrow()
let mainInst: SplideType | null = null
let thumbsInst: SplideType | null = null
let removeArrowListeners: (() => void) | null = null
defineExpose({
mainInst: computed(() => mainInst),
@@ -111,7 +112,7 @@ onMounted(() => {
mainInst.sync(thumbsInst)
// 썸네일 슬라이드의 화살표 버튼에 이벤트 리스너 추가
nextTick(() => {
addArrowClickListeners(thumbsInst, (direction, targetIndex) => {
removeArrowListeners = addArrowClickListeners(thumbsInst, (direction, targetIndex) => {
emit('arrowClick', direction, targetIndex)
})
})
@@ -119,8 +120,17 @@ onMounted(() => {
})
onBeforeUnmount(() => {
// 이벤트 리스너 제거
if (removeArrowListeners) {
removeArrowListeners()
removeArrowListeners = null
}
// Splide 인스턴스 정리
mainInst?.destroy?.()
thumbsInst?.destroy?.()
mainInst = null
thumbsInst = null
})
</script>

View File

@@ -16,6 +16,7 @@ export const useCheckGameStart = () => {
const isShowCheckLauncher = ref(false) // 런처 실행 로딩 표시
const isShowDownloadLauncher = ref(false) // 런처 다운로드 표시
const customerServiceUrl = `${stoveCs}/${gameData.value?.game_id}`
let launcherTimeoutId: ReturnType<typeof setTimeout> | null = null
// 에러 처리
const errorHandler = (errorCode: number) => {
@@ -64,13 +65,20 @@ export const useCheckGameStart = () => {
// 런처 실행 로딩 시작 UI 처리
const startLoadingForLauncher = () => {
if (import.meta.client) {
// 이전 타이머 정리
if (launcherTimeoutId) {
clearTimeout(launcherTimeoutId)
launcherTimeoutId = null
}
isShowCheckLauncher.value = true
isShowDownloadLauncher.value = false
setTimeout(() => {
launcherTimeoutId = setTimeout(() => {
if (isShowCheckLauncher.value) {
isShowDownloadLauncher.value = true
}
launcherTimeoutId = null
}, 5000)
}
}
@@ -78,6 +86,12 @@ export const useCheckGameStart = () => {
// 런처 실행 로딩 종료 UI 처리
const stopLoadingForLauncher = () => {
if (import.meta.client) {
// 타이머 정리
if (launcherTimeoutId) {
clearTimeout(launcherTimeoutId)
launcherTimeoutId = null
}
isShowCheckLauncher.value = false
isShowDownloadLauncher.value = false
}

View File

@@ -43,6 +43,7 @@ export const useSplideArrow = () => {
* 화살표 버튼에 클릭 이벤트 리스너를 추가하는 함수
* @param splide - Splide 인스턴스
* @param onArrowClick - 화살표 클릭 시 실행될 콜백 함수
* @returns 이벤트 리스너 제거 함수
*/
const addArrowClickListeners = (
splide: SplideType,
@@ -51,12 +52,25 @@ export const useSplideArrow = () => {
const prevArrow = splide.root.querySelector('.arrow-prev')
const nextArrow = splide.root.querySelector('.arrow-next')
const prevHandler = () => handleArrowClick('prev', splide, onArrowClick)
const nextHandler = () => handleArrowClick('next', splide, onArrowClick)
if (prevArrow) {
prevArrow.addEventListener('click', () => handleArrowClick('prev', splide, onArrowClick))
prevArrow.addEventListener('click', prevHandler)
}
if (nextArrow) {
nextArrow.addEventListener('click', () => handleArrowClick('next', splide, onArrowClick))
nextArrow.addEventListener('click', nextHandler)
}
// 이벤트 리스너 제거 함수 반환
return () => {
if (prevArrow) {
prevArrow.removeEventListener('click', prevHandler)
}
if (nextArrow) {
nextArrow.removeEventListener('click', nextHandler)
}
}
}
@@ -66,3 +80,4 @@ export const useSplideArrow = () => {
addArrowClickListeners
}
}

View File

@@ -7,11 +7,13 @@ export const useLoadingStore = defineStore('loadingStore', () => {
const isPAssApiLoading = ref(false)
// 컴포넌트별 로딩 표기 - Map 대신 일반 객체 사용
const localLoadings = ref<Record<string, { active: boolean }>>({})
const apiLoadingTimeoutId = ref<ReturnType<typeof setTimeout> | null>(null)
/**
* 로딩 상태 초기화
*/
const initializeStore = () => {
localLoadings.value = {}
hasApiCallStarted.value = false
isPAssApiLoading.value = false
@@ -34,10 +36,18 @@ export const useLoadingStore = defineStore('loadingStore', () => {
fullLoading.value = false
}
}
const finishApiLoading = () => {
setTimeout(() => {
// 이전 타이머가 있으면 정리
if (apiLoadingTimeoutId.value) {
clearTimeout(apiLoadingTimeoutId.value)
apiLoadingTimeoutId.value = null
}
apiLoadingTimeoutId.value = setTimeout(() => {
hasApiCallStarted.value = false
isPAssApiLoading.value = true
apiLoadingTimeoutId.value = null
}, 300)
}

View File

@@ -118,13 +118,22 @@ export const useModalStore = defineStore('modalStore', () => {
storeContentText: ref(''),
}
const toastTimeoutId = ref<ReturnType<typeof setTimeout> | null>(null)
const handleOpenToast = ({ contentText, duration = 2000 }: ToastParams) => {
// 이전 타이머가 있으면 정리
if (toastTimeoutId.value) {
clearTimeout(toastTimeoutId.value)
toastTimeoutId.value = null
}
toast.storeIsOpen.value = true
toast.storeContentText.value = contentText
setTimeout(() => {
toastTimeoutId.value = setTimeout(() => {
toast.storeIsOpen.value = false
toast.storeContentText.value = ''
toastTimeoutId.value = null
}, duration)
}

View File

@@ -24,6 +24,7 @@ const { sendLog, useAnalyticsLogDataDirect } = useAnalytics()
const slideThumbnailRef = ref<any>(null)
const playingSlideIndex = ref<number | null>(null)
let stopVideoTimeoutId: ReturnType<typeof setTimeout> | null = null
const backgroundData = computed(() =>
getComponentGroup(props.components, 'background')
@@ -89,12 +90,27 @@ const handleVideoClick = (index: number) => {
}
const stopVideo = () => {
// 이전 타이머 정리
if (stopVideoTimeoutId) {
clearTimeout(stopVideoTimeoutId)
stopVideoTimeoutId = null
}
// 전환 시간 후 완전히 제거
setTimeout(() => {
stopVideoTimeoutId = setTimeout(() => {
playingSlideIndex.value = null
stopVideoTimeoutId = null
}, 400)
}
onBeforeUnmount(() => {
// 타이머 정리
if (stopVideoTimeoutId) {
clearTimeout(stopVideoTimeoutId)
stopVideoTimeoutId = null
}
})
const onArrowClick = (direction, targetIndex) => {
const arrowGroupAry = getComponentGroupAry(props.components, 'arrow')
const logTracking = arrowGroupAry?.[direction === 'prev' ? 0 : 1]