웹으로 만든 채팅 토론 서비스에서 새로고침시 재입장 로직을 자동 실행시켜주려고 한다.
또한 페이지 이탈시(URL 직접 입력으로 페이지 이동) 확인 창을 띄워주고자 한다.
위 기능을 추가하면서 사용한 새로고침, 페이지 이탈에 대해 각각 다르게 감지한 방법을 작성하고자 한다.
페이지 이탈을 감지하는데 널리 알려진 이벤트로는 beforeunload
이벤트가 있다.
이 이벤트는 새로고침, 페이지 이탈, 탭 닫기 등 unload
이벤트가 발생하기 전에 유저에게 정말로 새로고침/페이지이탈 할 것인지 확인 창을 띄워줄 수 있도록 한다.
즉, beforeunload
이벤트 -> OK -> unload
이벤트 순으로 발생한다.
일단 새로고침이든 페이지 이탈이든 한 번 실행하고 나면 상태 데이터가 사라질 수 있는 위험이 있기에
경고차 확인 창을 띄우고자 했다.
커스텀 훅을 만들어 useEffect
로 이벤트를 추가해주었다.
const handleUnload = useCallback(() => {
webSocketClient?.deactivate(); // 소켓 연결 끊기
mutation?.(); // 채팅방 나가기 API 호출
}, [webSocketClient, mutation]);
const handleBeforeUnload = useCallback((e: BeforeUnloadEvent) => {
e.preventDefault(); // 브라우저에서 정말 새로고침/페이지이탈 할 것인지 묻는 확인 창이 뜨게 된다.
}, []);
useEffect(() => {
if (window) {
window.addEventListener('beforeunload', handleBeforeUnload);
window.addEventListener('unload', handleUnload);
}
return () => {
if (window) {
window.removeEventListener('beforeunload', handleBeforeUnload);
window.removeEventListener('unload', handleUnload);
}
};
}, [mutation, handleUnload, handleBeforeUnload]);
이렇게 작성하고 사용하고자 하는 페이지에 추가해주면 된다.
beforeunload
, unload
이벤트는 router 이벤트로는 실행되지 않는다.
유저가 브라우저상에서 제공하는 기능으로 페이지 이탈을 시도할 때 실행된다.!
이점을 참고하고 사용하면 된다.
mov -> gif 변환시 느려지는거 속 터지네
새로고침도 beforeunload
로 감지할 수 있지만, 새로고침은 결국 그 페이지에 남겠다는 것이다.
그래서 페이지 이탈과는 조금 다르다고 생각했고, 상태 초기화로 인해 유저가 재입장 해야하는 번거로움을 겪어야 하는게 싫었다.
그래서 새로고침으로 채팅방에 다시 들어오게 될 땐, 유저 정보를 가지고 있다가 재입장 API를 자동으로 호출하기로 했다.
그 과정에서 새로고침 감지가 필요했다.
이때 사용할 수 있는 코드는 다음과 같다.
performance.getEntriesByType('navigation')[0]
이 값은 4가지 중 하나이다.
혹시 back_forward
를 사용하려고 한다면, 아래의 포스트를 읽어본 후 사용하길 권한다.
window.performance back_forward 이벤트 미발생 오류, 해결
새로고침은 reload
를 사용하면 된다.
여기서, 타입스크립트는 그냥 사용하려고 하면 인식을 못해 에러가 발생한다.
const entries = performance.getEntriesByType(
'navigation',
)[0] as PerformanceNavigationTiming;
이렇게 선언해주고 사용하면 에러가 발생하지 않는다.
새로고침을 감지할 페이지에 아래 코드를 넣어주면 된다.
useEffect(() => {
if (typeof window !== 'undefined') {
const entries = performance.getEntriesByType(
'navigation',
)[0] as PerformanceNavigationTiming;
if (entries?.type === 'reload') {
// 코드
}
}
}, []);
// 브라우저 뒤로가기 버튼 클릭 시 페이지 이탈 방지 모달 띄우기
useEffect(() => {
const handlePopState = (event: PopStateEvent) => {
event.preventDefault();
window.history.pushState(null, '', window.location.pathname); // 뒤로가기 무효화
handleBack();
};
window.history.pushState(null, '', window.location.pathname); // 현재 상태 추가
window.addEventListener('popstate', handlePopState);
return () => {
window.removeEventListener('popstate', handlePopState);
};
}, []);
브라우저 상단에 위차한 앞으로가기/뒤로가기 버튼 클릭에 대한 이벤트를 넣고 싶을 때 위 코드를 사용하면 된다.
기존 이벤트를 무효화한 뒤 나는 정말 나갈 것인지 묻는 커스텀 창을 띄워주었다. (handleBack)
나는 두 가지 기능을 추가해주어야 했다.
1. 새로고침 시 재입장 API 호출 (reload)
2. URL 직접 변경으로 채팅방에 접근할 때 막기 (navigate)
일단 사용자 정보와 채팅방 정보를 가지고 있어야 재입장 API를 호출할 수 있으니, 데이터를 가지고 있어야 한다.
이를 위해 zustand의 persist
를 사용했다.
sessionStorage
에 데이터를 저장해 탭간 데이터 공유를 막고, 새로고침시에도 데이터가 날아가지 않도록 해주었다.
하지만 상당히 까다로운게, storage에 저장되는 것이니 페이지에서 강제로 나갈 때, 정상적으로 나갈 때, 페이지 접근 등 storage 데이터를 초기화하거나 저장하는 등 관리가 쉽지는 않았다.
어쩌겠어.. 해야지!
그렇게 작성한 코드는 다음과 같다.
const isSameAgora = (prevId: number, currentId: string | undefined) => {
if (prevId === Number(currentId)) {
return true;
}
return false;
};
useEffect(() => {
// 채팅방으로 페이지 이동시 다른 탭에서 이미 입장한 유저라면(session storage 데이터 없음) 내보내기
if (
typeof window !== 'undefined' &&
!isNull(session) &&
!hasMutated.current
) {
const entries = performance.getEntriesByType(
'navigation',
)[0] as PerformanceNavigationTiming;
if (entries?.type === 'reload') {
// 입장하기 api 호출
hasMutated.current = true;
mutation.mutate();
}
if (entries?.type === 'navigate' && isNull(agoraTitle)) {
// 채팅방 입장하기 페이지 띄우기
window.location.replace(`/flow/enter-agora/${agoraId}`);
} else if (
entries?.type === 'navigate' &&
!isSameAgora(prevAgoraId, agoraId)
) {
// 채팅방에서 다른 채팅방으로 이동, storage 데이터 초기화 후 입장하기 페이지 띄우기
agoraInfoReset();
userProfilReset();
selectedAgoraInfoReset();
useAgora.persist.rehydrate();
useEnter.persist.rehydrate();
window.location.replace(`/flow/enter-agora/${agoraId}`);
}
}
}, [session]);
hasMutated
은 로직 중복 실행을 방지하기 위해 추가해주었다.
이렇게 하면 새로고침 시에도 유저가 재입장을 해야하는 번거로움이 줄어든다!
그리고 다른 채팅방에서 강제로 넘어오려고 할 때도 막을 수 있다.