[React 4] 뒤로가기, 새로고침 막기

김헤일리·2023년 1월 10일
4

React

목록 보기
8/10

프로젝트를 진행하다가 어이없는 케이스를 발견했다.
뒤로가기 버튼을 여러번 누르면 이전에 게임을 진행했던 방에 들어갈 수 있는 것...

지금 생각해보면 당연한건데 진행하던 당시엔 생각도 못 했었다.
그러다 같은 반의 다른 팀에서 본인들은 뒤로가기와 새로고침을 아예 막아버렸다고 말을 해줬다.
그래서 나도 부랴부랴 뒤로가기와 새로고침을 막아서 게임 진행 시 일어날 수 있는 불상사를 막기로 했다.


1. History 객체와 Location

인터넷 브라우저를 오픈하면, 방문했던 탭을 확인할 때 사용하는 것이 history 객체라고 한다.
이 history 객체의 속성과 메소드를 이용하면 브라우저의 방문 기록을 조작할 수 있다고 한다.

  • 브라우저는 현재 탭의 방문 기록을 메모리 상에서 스택의 형태로 관리한다.
  • HTML5 history API는 탭 단위로 존재하는 window 전역 객체의 프로퍼티인 history 객체의 프로퍼티들과 메소드들로서 제공이 된다.
    • 예시: createBrowserHistory를 사용해서 콘솔에 출력

이렇게 history 객체를 생성했을 때 사용할 수 있는 여러 메소드들이 있다. 이 메소드들을 이용해서 웹브라우저의 탭을 조작할 수 있는데, 예를들어 history.back() 을 사용하면 브라우저는 자동적으로 세션 히스토리 상의 이전 페이지로 돌아간다.

history 객체의 프로퍼티 중 location이라는 것도 있는데, history.location의 경우, 해당 브라우저의 hash,key,pathname,search,state 값을 보여줬다.

  • 이때 Location이 표시하는 값은, react-router-dom에 있는 useLocation()에서 표시되는 값과 동일하다.

  • createBrowserHistory()를 사용하는 경우, useLocation을 따로 설치하거나 선언할 필요 없이 history.location을 이용해서 pathname을 가져올 수 있다.

    • pathname을 가져올 경우, url에 따라서 어떠한 이벤트를 실행시키고 싶을 때 유용하게 사용할 수 있다.


2. 뒤로가기를 감지하는 법

  • 브라우저 자체 뒤로가기를 막기 위해서 구글링을 통해 알아낸 custom hook을 사용했다.
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)이 변경될때마다,
};
  • history.push는 브라우저의 세션 기록 스택에 특정 상태를 추가한다.
    • 매개변수로 들어가는 것은 (state, title, url[optional])이다.

    • State:

      • 새로운 세션 기록 항목에 연결할 상태 객체를 의미한다.
      • 사용자가 새로운 상태로 이동할 때마다 popstate 이벤트가 발생하는데, 이 때 이벤트 객체의 state 속성에 해당 상태의 복제본이 담겨 있다.
    • Title:

      • 변경할 브라우저 제목을 작성할 수 있다.
      • 더 이상 사용하지 않는 매개변수지만, 만일을 대비하며 빈 문자열을 넣는 것을 추천한다고 한다.
      • 사용하지 않지만 삭제하지 못 한 이유는 이미 해당 부분을 적용해서 코딩을 진행한 사람이 있기 때문!
    • URL:

      • 새로운 세션 기록 항목의 URL.
      • pushState() 호출 이후에 브라우저는 주어진 URL로 탐색하지 않는다. 그러나 브라우저를 재시작할 경우 탐색을 시도할 수도 있다.
      • 상대 URL을 지정할 수 있으며, 이 땐 현재 URL을 기준으로 사용합니다.
      • 새로운 URL은 현재 URL과 같은 출처를 가져야 하며, 그렇지 않을 경우 예외가 발생합니다.
      • 지정하지 않은 경우 문서의 현재 URL을 사용한다.
  • 뒤로가기 버튼 클릭 시, 히스토리 스택에 새로운 location이 기록된다. 이때 popstate라는 이벤트가 일어난 셈이기 때문에 해당 이벤트를 감지하고 preventGoBack 함수가 실행된다.
    preventGoBack 실행 시, 현재 위치를 반환하며 토스트 메세지를 출력하게 되는 것.

원래 인터넷에 검색해서 나왔던 내용은 코드에 push가 아니라 pushState로 되어있었다. 하지만 코드를 동일하게 사용하자, pushState는 not a function이라는 오류를 내뱉었고, 그때 콘솔에 history 객체를 찍어보니 객체의 method가 pushState가 아니라 push로 표시되어 있었다.
검색해보니 버전이 달라서 그랬던 것 같다.



3. 새로고침을 감지하는 법

  • 새로고침을 감지하기 위해선 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를 하나 더 추가하는 방식밖에 없다고 하였다.

그래서 아쉽지만 일단 급한대로 새로고침으로 페이지를 나가기 전에 경고창을 한번 띄워 유저의 이탈을 최소한으로 막을 수 있는 방식을 하나 더 추가하였다.



지금은 아예 접근도 못 하게 막아버린 셈이지만 다음엔 더 나은 방식으로 구현할 수 있을까?

사실 더 나은 방법이 뭐가 있을 지 생각도 못 하겠다 😅

나중에 다른 서비스를 사용하면서 다른 사람들은 어떤 방식으로 이런 오류를 막았는지 확인해봐야겠다!


출처:

profile
공부하느라 녹는 중... 밖에 안 나가서 버섯 피는 중... 🍄

0개의 댓글