뒤로가기 이벤트 막고 원하는 이벤트 실행하기(React)
구현 과제 진행 중 페이지 이동은 하지않고 상태들만 뒤로 돌려야하는 이벤트가 필요했는데 제출할때는 결국 버그를 잡아내지 못했지만,, 이후에 정상작동하는 방법을 찾았습니다!
히스토리 객체
히스토리 객체는 브라우저의 히스토리에 대한 정보를 document, documentState 리스트로 저장하는 객체입니다. 자바스크립트에서 개인정보 보호를 위해 객체에 접근하여 상세한 정보를 알 수는 없습니다.
- history.length : 브라우저 히스토리 목록의 개수를 반환합니다.
- history.go() : 인수로 전달받은 정수만큼 히스토리 이동 ex:) history.go(-2) 2개의 이전페이지로 이동
- history.back() : 히스토리 목록의 이전 URL로 이동, 브라우저의 뒤로가기와 동일
- history.forward() : 히스토리 목록의 다음 URL로 이동, 브라우저의 앞으로가기와 동일
위에서 얘기했듯 히스토리는 객체지만 히스토리 리스트는 내부에 자료구조 Stack 형태의 리스트로 구성되어 있습니다. 뒤로가기 버튼을 누르면 window객체에 설정된 popstate 이벤트가 실행되고 기본 이벤트로 설정된 뒤로 또는 앞으로가기가 실행됩니다. (history의 앞뒤는 모두 "POP" 이벤트로 처리되어 구분할 수 없습니다.)
뒤로가기 이벤트 막기
뒤로가기 이벤트를 막는 방법은 꽤 여러가지 방법이 있습니다! react-router v5의 경우에는 useHistory 훅을 이용했지만 저는 v6를 사용하기에 전역 history객체를 사용했습니다. (v6에서는 useHistory 훅이 없어졌습니다. 페이지 이동 시 useNavigate 사용)
router 이동 시 발생하는 popstate의 기본 이벤트를 막을 수는 없습니다. history.pushState
를 이용해 페이지 이동없이 현재 히스토리 스택을 추가하고 뒤로가기 버튼으로 인해 뒤로 이동해서 현재의 페이지를 유지시키는 방법입니다. 만약 홈 -> 로그인 -> 게시판으로 이동했고 게시판에서 뒤로가기 이벤트를 막아준다면 이런형태가 됩니다. (pushState와 replaceState 는 popstate 이벤트를 발생시키지 않습니다.)
사실 히스토리 리스트에서 최상단 요소가 pop()되는지는 확실하지 않습니다.. 제가 적용했을 때 그림과 같은 형태로 useEffect를 실행하면 앞으로가기 버튼이 잠깐 활성화 했다가 사라지는 모습을 보고 유추했습니다. 동작은 원하는 대로 진행되었으니까요! 아쉽게도 브라우저는 앞으로가기 이벤트와 뒤로가기 이벤트를 별도로 구분해놓지 않아 두 이벤트 모두 "POP" 이벤트로 처리되므로 저처럼 앞으로 갈 경우 상태의 처리가 곤란하다면 앞으로 가기를 막는것도 하나의 방법인거 같습니다 👍 (gif라 프레임이 잘려 잘 보이지 않네요😅)
React 적용(버그 존재 ❗️)
진행한 사전과제로 예시를 들수는 없으므로 cra로 간단하게 프로젝트를 만든 후 홈, 로그인, 게시판 페이지를 router로 연결해 줍니다! 문제만 보여주기 위해 useEffect의 로직만 보여드리겠습니다.
페이지를 유지하고 뒤로가기를 막는것까지는 문제없이 진행이 되지만, pushState
시에 현재의 주소가 들어가게 되니, 게시판 페이지에서 마지막 뒤로가기를 눌러 login으로 이동 하고 나서 다시 뒤로가기를 누르면 마운트 시에 들어간 pushState인 게시판 페이지가 나오고, 다시 뒤로가기를 누르면 조건문의 navagate("/login")이 실행되고, 무한반복이 되어버렸습니다.
이유는 이제서야 알게되었는데 천천히 되짚어 보면 navagate('/login')
을 통해 로그인 페이지로 이동을 했지만 history.back() 즉 스택을 뒤로간게 아니라 로그인 페이지로 이동한 히스토리 스택을 추가한 형태가 되고 그럼 당연히 이전페이지는 notice페이지가 되는 거였습니다.
React 적용(버그 해결)
이걸 어떻게 해결해야할지 이것저것 많은 시도를 해보았더니 첫번째로 알게된건 세번째 인자에 빈 문자열을 넣어주면 됩니다! mdn 문서에 따르면 빈 문자열을 넣게되면 알아서 현재 document의 url로 설정이 됩니다. 굳이 적어주지 않아도 된다는거죠. 그리고 내가 원하는건 뒤로가기니까 navigate(-1)
형태로 주었어요.
이 코드는 뒤로가고 난 후에는 문제가 없습니다. 하지만 흘러가는 형태를 보게되면 이벤트가 반복실행 되는게 보이죠.
- notice 페이지 진입 (pushState 2번 실행)
- 클릭이벤트 4번 진행 -> (pushState 4번 실행)
- popstate 이벤트(뒤로가기) 진행 -> navigate(-1) 로 인해 뒤로 이동 -> 뒤로이동 했으므로 다시 popstate 진행 -> 쌓인 notice페이지(6번) 만큼 실행 후 페이지이동
만약 20가지 선택을 하는 페이지였다면 뒤로가기위해 저 루프를 20번이상을 반복해야 하는거죠.. 하지만 앞으로가기 버튼을 비활성화 시키기 위해서 pushState는 필수였고 그냥 이 루틴으로 픽스하였습니다.
sessionStorage와 쿼리스트링을 이용한 방법도 가능하겠다는 생각이 드네요. sessionStorage는 동일하게 뒤로가기 이벤트를 막는 pushState가 필요할 것 같고 쿼리스트링을 이용하면 가장 깔끔하게 될거 같습니다!
브라우저의 기본적인 이벤트를 막고 진행을 해야한다는 점이 어떤 에러를 일으킬지 몰라 사실 historyAPI는 지양하는게 좋다지만.. 2일을 붙잡아도 다른 방법이 떠오르지 않네요 😅 추후 기회가 된다면 쿼리스트링으로 업데이트 해보겠습니다!
❗️ (2024-11-25) 정말 꼭 사용해야 하는게 아니라면 쿼리스트링을 이용하세요!