본문 바로가기

TroubleShooting

사용자 이탈 방지 로직(뒤로가기/새로고침 차단) 최적화

반응형

1. 발생 이슈

활동 일지 작성 페이지에서 사용자가 입력 중인 데이터가 실수로 유실되는 것을 방지하기 위해 이탈 방지 로직을 구현했으나, 다음과 같은 기술적 결함이 발생했습니다.

  • 히스토리 스택 오염: 데이터 입력 시마다 브라우저 히스토리에 불필요한 엔트리가 계속 추가됨.
  • 뒤로가기 기능 오작동: 이탈 확인 창에서 '확인'을 눌러도 이전 페이지로 이동하지 못하고 현재 페이지에 갇힘.
  • 라이브러리 제약: useBlocker 등 최신 API 도입이 어려운 환경에서 브라우저 네이티브 API(popstate) 제어 로직의 불안정성 노출.

2. 원인 분석

  • 잘못된 의존성 배열 설정: useEffect 내 로직이 formDataselectedFiles의 변화에 반응하도록 설정되어, 값이 수정될 때마다 이벤트 리스너가 재등록되고 pushState가 중복 실행됨.
  • 이벤트 루프 제어 실패: handlePopState 내부에서 pushStateback() 호출 순서가 꼬이면서 브라우저가 정상적인 히스토리 이동 경로를 찾지 못함.

3. 해결 방법

계층화된 아키텍처 원칙에 따라 컴포넌트 마운트 시점에 로직을 분리하고, 불필요한 리렌더링 및 히스토리 추가를 방지하도록 리팩토링했습니다.

① 이탈 방지 플래그 관리 (useRef)

상태 변화 시마다 이벤트를 재등록하지 않도록 useRef를 사용하여 최신 데이터 변경 여부를 관리합니다.

TypeScript

const isFormDirtyRef = useRef(false);

// 폼 데이터 변경 감지  
useEffect(() => {  
if (initialFormDataRef.current) {  
const hasChanges = JSON.stringify(formData) !== JSON.stringify(initialFormDataRef.current);  
isFormDirtyRef.current = hasChanges || selectedFiles.length > 0;  
}  
}, \[formData, selectedFiles\]);\

② 브라우저 히스토리 및 이벤트 최적화 (useEffect)

컴포넌트 로드 시 단 한 번만 더미 엔트리를 추가하고, 확인 버튼 클릭 시에만 리스너를 제거 후 이동하도록 로직을 단순화했습니다.

TypeScript

useEffect(() => {  
const handlePopState = (e: PopStateEvent) => {  
if (isFormDirtyRef.current && !submitting) {  
const confirmed = window.confirm("작성 중인 내용이 있습니다. 페이지를 떠나시겠습니까?");
  if (!confirmed) {
    // 취소 시: 현재 페이지 유지 (뒤로 간 스택을 다시 앞으로 밀어넣음)
    window.history.pushState(null, "", window.location.href);
  } else {
    // 확인 시: 이벤트 리스너 제거 후 실제 뒤로가기 수행
    isFormDirtyRef.current = false;
    window.removeEventListener("popstate", handlePopState);
    window.history.back();
  }
}


};

window.addEventListener("popstate", handlePopState);  
window.history.pushState(null, "", window.location.href); // 초기 1회만 더미 엔트리 추가

return () => {  
window.removeEventListener("popstate", handlePopState);  
};  
}, \[submitting\]); // formData 의존성 제거로 중복 실행 방지\`

4. 결과 및 배운점

결과

  • 사용자 경험 개선: 데이터 수정 후 뒤로가기 시 알림창이 정상적으로 뜨며, '확인' 클릭 시 즉시 이전 페이지로 이동함.
  • 성능 및 안정성: 브라우저 히스토리에 더미 스택이 쌓이지 않아 뒤로가기 버튼을 길게 눌러도 깨끗한 이력을 유지함.
  • 예외 처리 완성: 새로고침(beforeunload)과 뒤로가기(popstate)를 모두 완벽히 차단하여 데이터 유실 방지.

배운점 (Key Takeaways)

  • 상태 참조 최적화: useEffect 내에서 실시간 상태값이 필요할 때, 의존성 배열에 넣어 함수를 재실행하기보다 useRef를 활용하는 것이 부수 효과(Side Effect)를 제어하는 데 더 효율적임을 학습함.
  • 브라우저 동작 원리: SPA 환경에서 브라우저의 히스토리 스택이 어떻게 작동하는지 깊이 이해하게 되었으며, 네이티브 API 제어의 중요성을 깨달음.
반응형