프로젝트를 진행하다가 어이없는 케이스를 발견했다.
뒤로가기 버튼을 여러번 누르면 이전에 게임을 진행했던 방에 들어갈 수 있는 것...
지금 생각해보면 당연한건데 진행하던 당시엔 생각도 못 했었다.
그러다 같은 반의 다른 팀에서 본인들은 뒤로가기와 새로고침을 아예 막아버렸다고 말을 해줬다.
그래서 나도 부랴부랴 뒤로가기와 새로고침을 막아서 게임 진행 시 일어날 수 있는 불상사를 막기로 했다.
인터넷 브라우저를 오픈하면, 방문했던 탭을 확인할 때 사용하는 것이 history 객체라고 한다.
이 history 객체의 속성과 메소드를 이용하면 브라우저의 방문 기록을 조작할 수 있다고 한다.
createBrowserHistory
를 사용해서 콘솔에 출력이렇게 history 객체를 생성했을 때 사용할 수 있는 여러 메소드들이 있다. 이 메소드들을 이용해서 웹브라우저의 탭을 조작할 수 있는데, 예를들어 history.back()
을 사용하면 브라우저는 자동적으로 세션 히스토리 상의 이전 페이지로 돌아간다.
history 객체의 프로퍼티 중 location이라는 것도 있는데, history.location
의 경우, 해당 브라우저의 hash
,key
,pathname
,search
,state
값을 보여줬다.
이때 Location이 표시하는 값은, react-router-dom
에 있는 useLocation()
에서 표시되는 값과 동일하다.
createBrowserHistory()
를 사용하는 경우, useLocation
을 따로 설치하거나 선언할 필요 없이 history.location
을 이용해서 pathname
을 가져올 수 있다.
const usePreventGoBack = () => {
const history = createBrowserHistory();
// 1. history라는 상수에 createBrowerHistory 함수를 할당한다.
const preventGoBack = () => {
// 2. custom hook에서 실행될 함수를 생성하고, 함수명을 preventGoBack으로 설정한다.
history.push(null, '', history.location.href);
// 2-1. 현재 상태를 세션 히스토리 스택에 추가(push)한다.
useToast('뒤로 갈 수 없닭! 🐓');
// 2-2. 토스트 메세지를 출력한다.
};
// 브라우저에 렌더링 시 한 번만 실행하는 코드
useEffect(() => {
(() => {
history.push(null, '', history.location.href);
// 3. 렌더링 완료 시 현재 상태를 세션 히스토리 스택에 추가(push)한다.
window.addEventListener('popstate', preventGoBack);
// 3-1. addEventListener를 이용해 "popstate"라는 이벤트를 감지하게 한다.
// 3-2. popstate 이벤트를 감지했을 때 preventGoBack 함수가 실행된다.
})();
return () => {
window.removeEventListener('popstate', preventGoBack);
// 3-3. 렌더링이 끝난 이후엔 eventListner을 제거한다.
};
}, []);
useEffect(() => {
history.push(null, '', history.location.href);
// 4-1. 현재 상태를 세션 히스토리 스택에 추가(push)한다.
}, [history.location]);
// 4. history.location (pathname)이 변경될때마다,
};
매개변수로 들어가는 것은 (state, title, url[optional])이다.
State:
Title:
URL:
원래 인터넷에 검색해서 나왔던 내용은 코드에 push
가 아니라 pushState
로 되어있었다. 하지만 코드를 동일하게 사용하자, pushState는 not a function
이라는 오류를 내뱉었고, 그때 콘솔에 history 객체를 찍어보니 객체의 method가 pushState
가 아니라 push
로 표시되어 있었다.
검색해보니 버전이 달라서 그랬던 것 같다.
beforeUnload
라는 브라우저 고유 이벤트를 감지해야한다.const usePreventRefresh = () => {
// 1. custom hook으로 사용할 함수를 하나 생성한다.
const preventClose = (e) => {
// 2. 해당 함수 안에 새로운 함수를 생성하는데, 이때 이 함수는 자바스크립트의 이벤트를 감지하게된다.
e.preventDefault();
// 2-1. 특정 이벤트에 대한 사용자 에이전트 (브라우저)의 기본 동작이 실행되지 않도록 막는다.
e.returnValue = '';
// 2-2. e.preventDefault를 통해서 방지된 이벤트가 제대로 막혔는지 확인할 때 사용한다고 한다.
// 2-3. 더 이상 쓰이지 않지만, chrome 설정상 필요하다고 하여 추가함.
// 2-4. returnValue가 true일 경우 이벤트는 그대로 실행되고, false일 경우 실행되지 않는다고 한다.
};
// 브라우저에 렌더링 시 한 번만 실행하는 코드
useEffect(() => {
(() => {
window.addEventListener('beforeunload', preventClose);
// 4. beforeunload 이벤트는 리소스가 사라지기 전 window 자체에서 발행한다.
// 4-2. window의 이벤트를 감지하여 beforunload 이벤트 발생 시 preventClose 함수가 실행된다.
})();
return () => {
window.removeEventListener('beforeunload', preventClose);
// 5. 해당 이벤트 실행 후, beforeunload를 감지하는 것을 제거한다.
};
});
};
새로고침 시 알림창이 아니라 뒤로가기 방지처럼 토스트 메세지만 띄우고 새로고침을 하지 못 하게 막는 방법을 찾아보고 싶었는데, 내 구글링 실력이 부족해서인지 찾을 수 없었다. 내가 검색해서 찾은 결과로는 새로고침 버튼을 아예 막는 것은 불가능하고 위에처럼 알림창으로 depth를 하나 더 추가하는 방식밖에 없다고 하였다.
그래서 아쉽지만 일단 급한대로 새로고침으로 페이지를 나가기 전에 경고창을 한번 띄워 유저의 이탈을 최소한으로 막을 수 있는 방식을 하나 더 추가하였다.
지금은 아예 접근도 못 하게 막아버린 셈이지만 다음엔 더 나은 방식으로 구현할 수 있을까?
사실 더 나은 방법이 뭐가 있을 지 생각도 못 하겠다 😅
나중에 다른 서비스를 사용하면서 다른 사람들은 어떤 방식으로 이런 오류를 막았는지 확인해봐야겠다!
출처: