Explanation
오늘은 popstate 이벤트에서 앞으로가기, 뒤로가기를 구분하는 방법에 대해 적어보려 합니다.
바닐라로 적을지 리액트로 적을지 고민하다가 바닐라가 기본이긴 하지만 보편적으로 리액트를 더 많이 쓸 꺼 같아서 리액트로 적었습니다!
작성된 코드는 https://github.com/falsy/blog-post-example/blob/master/popstate-direction/src/index.js 에서 확인하실 수 있습니다!
1. 전체 코드
import { useState, useEffect, useCallback } from 'react'
import ReactDOM from 'react-dom/client'
const container = document.getElementById('wrap')
const root = ReactDOM.createRoot(container)
const App = () => {
const [historyIndex, setHistoryIndex] = useState(0)
const [direction, setDirection] = useState('None')
useEffect(() => {
const index = window.history.state?.index
if(index) {
setHistoryIndex(index)
} else {
window.history.replaceState({ index: 0 }, '')
}
}, [])
useEffect(() => {
window.history.pushState = (state, ...arg) => {
const index = historyIndex + 1
setHistoryIndex(index)
return History.prototype.pushState.apply(
window.history,
[{ ...state, index }, ...arg]
)
}
window.history.replaceState = (state, ...arg) => {
return History.prototype.replaceState.apply(
window.history,
[{ ...state, index: historyIndex }, ...arg]
)
}
window.addEventListener('popstate', onPopstate)
return () => {
window.removeEventListener('popstate', onPopstate)
}
}, [historyIndex])
const onPopstate = useCallback(() => {
const { state } = window.history
if (!state) window.history.replaceState({ index: historyIndex + 1 }, '')
const index = state ? state.index : historyIndex + 1
setHistoryIndex(index)
setDirection(index > historyIndex ? 'forward' : 'back')
}, [historyIndex])
return (
<div>
<h1>Direction: {direction}</h1>
<ul>
<li onClick={() => window.history.pushState({}, '', '#1')}>#1</li>
<li onClick={() => window.history.pushState({}, '', '#2')}>#2</li>
<li onClick={() => window.history.pushState({}, '', '#3')}>#3</li>
</ul>
</div>
)
}
root.render(
<App />
)
2. 바로 시작
const App = () => {
const [historyIndex, setHistoryIndex] = useState(0)
const [direction, setDirection] = useState('None')
...
histroyIndex는 히스토리가 증감할 때마다 index 값을 늘리고 줄여줘서 이 값을 기준으로 popstate에서 앞, 뒤 구분을 해줄 거에요.
useEffect(() => {
const index = window.history.state?.index
if(index) {
setHistoryIndex(index)
} else {
window.history.replaceState({ index: 0 }, '')
}
}, [])
...
처음 페이지에 들어오면 history 객체의 state 값에 index 값이 있는지 확인합니다.
(새로고침으로 접근한 거라면 기존에 입력한 index 값이 있을 거예요.)
그리고 없다면 index 값을 초깃값으로 0으로 replaceState 해줍니다.
useEffect(() => {
window.history.pushState = (state, ...arg) => {
const index = historyIndex + 1
setHistoryIndex(index)
return History.prototype.pushState.apply(
window.history,
[{ ...state, index }, ...arg]
)
}
window.history.replaceState = (state, ...arg) => {
return History.prototype.replaceState.apply(
window.history,
[{ ...state, index: historyIndex }, ...arg]
)
}
window.addEventListener('popstate', onPopstate)
return () => {
window.removeEventListener('popstate', onPopstate)
}
}, [historyIndex])
...
이제 pushState와 replaceState를 재정의해 줄 거에요. 왜냐면...
글을 적다 보니... 문득,
생각해 보니 그냥 서비스 내에서 사용하는 거라면 굳이 재정의 하지 말고 별개로 함수를 만들어 쓰는 게 더 좋을 거 같네요.
const handleClickPushState = useCallback((to) => {
window.history.pushState({ index: historyIndex + 1}, '', to)
}, [historyIndex])
const handleClickReplaceState = useCallback((to) => {
window.history.replaceSteate({ index: historyIndex }, '', to)
}, [historyIndex])
그냥 위와 같이 함수를 만들어서 쓰는 게 더 좋겠네요.
기본 내장 객체의 함수를 재정의하는 건.. 뭔가.. 뭔가.. 하니까..
보통 이렇게 잘못된 게 있으면 글을 처음으로 돌아가 고쳐 적는 게 일반적이지만, 이 또한 또 소소한 개발 블로그 포스팅의 맛이 아니겠습니까? (라고 쓰고 귀찮아서 인건 안비밀..)
그리고 아래 popstate 이벤트를 등록해 주고 historyIndex 값을 디펜던시로 추가해 주었습니다.
const onPopstate = useCallback(() => {
const { state } = window.history
if (!state) window.history.replaceState({ index: historyIndex + 1 }, '')
const index = state ? state.index : historyIndex + 1
setHistoryIndex(index)
setDirection(index > historyIndex ? 'forward' : 'back')
}, [historyIndex])
...
드디어 대망의? popstate 이벤트 함수를 정의해 주었습니다.
우선 state 값이 있는지 확인합니다! 그리고 state가 없다면 앞으로의 히스토리 index 비교 관리를 위해서, index 값을 historyIndex + 1로 추가서 replaceState 해줍니다.
히스토리가 추가됐는데 state 값이 없는 상황은, 예를 들면 window.location.hash = 'hello' 이렇게 해시를 추가해서 히스토리가 추가되는 경우가 있겠네요!
그리고 현재의 index 값을 state.index 값이 있다면 state.index 값으로, 없다면 historyindex + 1 으로 주면!
여기에서 index 값이 historyIndex 값보다 크면 브라우저에서 앞으로가기 했을 때가 되고 아니라면 뒤로 가기 상황이라는 걸 확인할 수 있답니다.
마지막으로 index 값을 다시 setHistoryIndex() 해주어서 다음 기준값으로 등록해 줍니다.



