diff --git a/src/hooks/useBeforeUnload.ts b/src/hooks/useBeforeUnload.ts index 85364da..f3e66d7 100644 --- a/src/hooks/useBeforeUnload.ts +++ b/src/hooks/useBeforeUnload.ts @@ -1,16 +1,31 @@ import { useEffect } from 'react'; +import usePreservedCallback from './usePreservedCallback'; interface UseBeforeUnloadProps { enabled?: boolean; + beforeUnloadAction?: (event: BeforeUnloadEvent) => void; } const useBeforeUnload = ({ enabled = true, // 기본적으로 이벤트 바인딩 + beforeUnloadAction, // noop으로 기본 함수 설정도 가능 }: UseBeforeUnloadProps = {}): void => { - const handleBeforeUnload = (event: BeforeUnloadEvent) => { - event.preventDefault(); - return (event.returnValue = ''); - }; + // usePreservedCallback는 함수의 참조를 유지해주는 커스텀 훅입니다. @modern-kit 참고 + // props로 전달하는 함수의 참조를 유지해야되는 이유는 함수도 결국 객체이기 때문에 props로 전달하는 함수는 리렌더링마다 재생성되기 때문에 + // useCallback으로 관리하더라도 매번 재생성됩니다. 이런 문제를 해결하기 위해 usePreservedCallback와 같은 훅을 사용 + //내부적으로 ref를 통해 최신 콜백함수 유지 useCallback을 통해 함수 참조값 메모이제이션 + const handleBeforeUnload = usePreservedCallback( + (event: BeforeUnloadEvent) => { + event.preventDefault(); + + // beforeUnloadAction이 있을 경우 호출 + if (beforeUnloadAction) { + beforeUnloadAction(event); + } + return (event.returnValue = ''); + } + ); + useEffect(() => { if (!enabled) return; // enabled가 false이면 이벤트 바인딩하지 않음 @@ -19,6 +34,6 @@ const useBeforeUnload = ({ return () => { window.removeEventListener('beforeunload', handleBeforeUnload); }; - }, [enabled]); + }, [enabled, handleBeforeUnload]); }; export default useBeforeUnload; diff --git a/src/hooks/usePreservedCallback.ts b/src/hooks/usePreservedCallback.ts new file mode 100644 index 0000000..ca60b8a --- /dev/null +++ b/src/hooks/usePreservedCallback.ts @@ -0,0 +1,29 @@ +import { useCallback, useRef } from 'react'; +import noop from '../utils/noop'; + +/** + * @description 주어진 콜백 함수의 `참조를 유지`하고, 컴포넌트 렌더링 사이에 재사용할 수 있도록 도와주는 커스텀 훅입니다. + * + * 이 훅은 특히 콜백 함수가 렌더링 중에 변경될 때 유용합니다. 불필요한 함수 생성을 방지하고 최적화하며, 최신 버전의 콜백 함수를 사용 할 수 있습니다. + * + * @template T - 콜백 함수의 타입. + * @param {T | undefined} [callback=noop] - 유지하고자 하는 콜백 함수. + * @returns {T} 최신 콜백 함수 참조를 사용하는 메모이제이션된 콜백 함수. + * + * @example + * const preservedCallback = usePreservedCallback(callback); + * + * preservedCallback(); + */ +const usePreservedCallback = any>( + callback: T = noop as T +): T => { + const callbackRef = useRef(callback); + + callbackRef.current = callback; + + return useCallback((...args: any[]) => { + return callbackRef.current(...args); + }, []) as T; +}; +export default usePreservedCallback; diff --git a/src/pages/quiz/Quiz.tsx b/src/pages/quiz/Quiz.tsx index 89cf456..49b8c02 100644 --- a/src/pages/quiz/Quiz.tsx +++ b/src/pages/quiz/Quiz.tsx @@ -13,7 +13,7 @@ import ResultModal from '../../features/quiz/ui/ResultModal'; import getParams from '../../utils/getParams'; import TotalResults from '../../features/quiz/ui/TotalResults'; import { useState } from 'react'; -import arraysEqual from '../../utils/arraysEqual'; +import isArrayContentEqual from '../../utils/arraysEqual'; import { ResponseButton, SubmitSection } from '../../features/quiz/styles'; import QuizzesQuery from '../../queries/quizzesQuery'; import useMoadl from '../../hooks/useModal'; @@ -24,7 +24,7 @@ export default function Quiz() { useClientQuizStore(); const [result, setResult] = useState(false); const { Modal, closeModal, openModal, isShow } = useMoadl(); - //새로고침 시 WaringAlert가 뜨게해주는 훅 + //페이지 이탈시 경고창이 뜨는 훅 useBeforeUnload(); //추후에 url에서 추출이 아닌 내부적으로 props로 전달하는 로직으로 변경 예정 const [partId] = getParams(['part-id']); @@ -82,7 +82,7 @@ export default function Quiz() { { - setResult(arraysEqual(userResponseAnswer, answer)); + setResult(isArrayContentEqual(userResponseAnswer, answer)); openModal(); }} > diff --git a/src/utils/arraysEqual.ts b/src/utils/arraysEqual.ts index c4a8dd3..deedfbc 100644 --- a/src/utils/arraysEqual.ts +++ b/src/utils/arraysEqual.ts @@ -1,17 +1,22 @@ /** - * @description 2개의 배열을 비교하는 함수입니다. + * @description 2개의 1차원 배열을 순서와 내부 값이 일치하는지 비교하는 함수입니다. * - * @template T 배열 내부 요소의 타입 * - * @param T[] array 첫번째 array - * @param T[] array 두번째 array + * @param array1 첫번째 array + * @param array2 두번째 array * * @returns boolean값 * - * @example arraysEquial([1,2,3,4,5],[1,2,3,4,1]); + * @example isArrayContentEqual([1,2,3,4,5],['a',2,3,1]); * //false */ -const arraysEqual = (array1: T[], array2: T[]) => { - return JSON.stringify(array1) === JSON.stringify(array2); +const isArrayContentEqual = (array1: any[], array2: any[]): boolean => { + if (array1.length !== array2.length) return false; + for (let i = 0; i < array1.length; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; }; -export default arraysEqual; +export default isArrayContentEqual; diff --git a/src/utils/noop.ts b/src/utils/noop.ts new file mode 100644 index 0000000..f92c973 --- /dev/null +++ b/src/utils/noop.ts @@ -0,0 +1,10 @@ +/** + * @description 아무 작업도 수행하지 않는 함수입니다. + * 주로 콜백 함수나 기본 동작이 필요하지 않은 경우에 사용됩니다. + * + * @returns {void} - 아무런 값도 반환하지 않는 `Promise`를 반환합니다. + * + * @example + * noop(); // 이 함수는 아무런 작업도 하지 않고 종료됩니다. + */ +export default function noop(): void {}