diff --git a/files/ko/web/api/history_api/working_with_the_history_api/index.md b/files/ko/web/api/history_api/working_with_the_history_api/index.md index 09611d4c398e62..46bab2ed5521e2 100644 --- a/files/ko/web/api/history_api/working_with_the_history_api/index.md +++ b/files/ko/web/api/history_api/working_with_the_history_api/index.md @@ -2,108 +2,188 @@ title: History API로 작업하기 slug: Web/API/History_API/Working_with_the_History_API l10n: - sourceCommit: 55de68017f98094f45addb3ebaa0f7f52024f60b + sourceCommit: bc7e82aa6db60568d7146ee285918550bbe4b8ce --- {{DefaultAPISidebar("History API")}} -{{DOMxRef("History.pushState","pushState()")}} 및 {{DOMxRef("History.replaceState","replaceState()")}}메서드는 각각 히스토리 항목을 추가하고 수정합니다. 이 메서드들은 {{domxref("Window/popstate_event", "popstate")}} 이벤트와 함께 작동합니다. +History API는 웹사이트가 브라우저의 세션 기록과 상호작용할 수 있게 해줍니다. 세션 기록이란 사용자가 특정 창에서 방문한 페이지들의 목록을 말합니다. 예를 들어 사용자가 링크를 클릭하여 새로운 페이지를 방문하면, 그 새로운 페이지들이 세션 기록에 추가됩니다. 사용자는 브라우저의 "뒤로" 및 "앞으로" 버튼을 사용하여 기록을 앞뒤로 이동할 수도 있습니다. -## 히스토리 항목 추가 및 수정 +History API에서 정의된 주요 인터페이스는 {{domxref("History")}} 인터페이스이며, 이 인터페이스는 두 가지 전혀 다른 메서드 집합을 정의합니다. -{{DOMxRef("History.pushState","pushState()")}}을 사용하면 상태를 변경한 후 생성된 {{domxref("XMLHttpRequest")}}객체의 HTTP 헤더에 사용되는 리퍼러가 변경됩니다. 리퍼러는 {{domxref("XMLHttpRequest")}} 객체를 생성할 때 창이 `this`인 문서의 URL이 됩니다. +1. 세션 기록의 페이지로 이동하는 메서드 -### pushState() 메서드 예제 + - {{domxref("History.back()")}} + - {{domxref("History.forward()")}} + - {{domxref("History.go()")}} -`https://mozilla.org/foo.html`이 다음 JavaScript 코드를 실행한다고 가정합니다. +2. 세션 기록을 수정하는 메서드 -```js -const stateObj = { - foo: "bar", -}; - -history.pushState(stateObj, "page 2", "bar.html"); -``` + - {{domxref("History.pushState()")}} + - {{domxref("History.replaceState()")}} -이렇게 하면 URL 표시줄에 `https://mozilla.org/bar.html`이 표시되지만, 브라우저가 `bar.html`를 로드하거나 `bar.html`이 존재하는지 확인하지는 않습니다. +이 가이드에서는 더 복잡한 동작을 가지는 두 번째 메서드 집합에만 초점을 맞출 것입니다. -이제 사용자가 `https://google.com`로 이동한 다음 **뒤로가기**버튼을 클릭한다고 가정해 보겠습니다. 이 시점에서 URL 표시줄에 `https://mozilla.org/bar.html`이 표시되고 `history.state`에 `stateObj`가 포함됩니다. 페이지가 새로고침되었기 때문에 `popstate` 이벤트는 발생하지 않습니다. 페이지 자체는 `bar.html`처럼 보일 것입니다. +`pushState()` 메서드는 세션 기록에 새 항목을 추가하고, `replaceState()` 메서드는 현재 페이지의 세션 기록 항목을 업데이트합니다. 이 두 메서드는 모두 `state` 매개변수를 사용하며, 이는 어떤 {{Glossary("Serializable_object", "직렬화 가능한 객체")}}도 포함할 수 있습니다. 브라우저가 이 히스토리 항목으로 네비게이트할 때, 브라우저는 이 항목과 연관된 상태 객체를 포함하는 {{domxref("Window.popstate_event", "popstate")}} 이벤트를 발생시킵니다. -사용자가 **뒤로가기**를 다시 한 번 클릭하면, URL이 `https://mozilla.org/foo.html`로 변경되고 이번에는 문서에 `null` 상태 객체가 포함된 `popstate` 이벤트가 발생합니다. 여기서도 뒤로 돌아간다고 해서 문서의 내용이 이전 단계의 내용과 바뀌지는 않지만 문서가 `popstate` 이벤트를 수신하면 내용을 수동으로 업데이트할 수 있습니다. +이 API의 주요 목적은 {{Glossary("SPA", "싱글 페이지 애플리케이션")}}와 같은 웹사이트를 지원하는 것입니다. 이러한 웹사이트는 {{domxref("Window/fetch", "fetch()")}}와 같은 JavaScript API를 사용하여 전체 새로운 페이지를 로드하는 대신 새로운 콘텐츠로 페이지를 업데이트합니다. -### pushState() 메서드 +## 싱글 페이지 애플리케이션과 세션 기록 -`pushState()`는 **state object**, **title**(현재 무시됨), **URL**(선택사항)의 세 가지 매개변수를 가집니다. +전통적으로, 웹사이트는 페이지 모음으로 구현됩니다. 사용자가 링크를 클릭하여 사이트의 다른 부분으로 이동할 때마다 브라우저는 매번 완전한 새 페이지를 로드합니다. -이 세 가지 매개변수를 각각 자세히 살펴보겠습니다. +이는 많은 사이트에 적합하지만, 몇 가지 단점이 있을 수 있습니다. -- **state object** - - : 상태 객체는 `pushState()`에 의해 생성된 새 기록 항목과 연관된 JavaScript 객체입니다. 사용자가 새 상태로 이동할 때마다 `popstate` 이벤트가 발생하고 이벤트의 `state` 속성에는 기록 항목의 상태 개체 복사본이 포함됩니다. 상태 객체는 직렬화할 수 있는 모든 것이 될 수 있습니다. 상태 객체는 사용자가 브라우저를 재시작한 후 복원할 수 있도록 사용자의 디스크에 저장되므로 상태 객체의 직렬화된 표현에 640k라는 크기 제한을 적용합니다. 직렬화된 표현이 이보다 큰 상태 객체를 `pushState()`에 전달하면 메서드는 예외를 던집니다. 이보다 더 많은 공간이 필요한 경우, `sessionStorage` 또는 `localStorage`사용을 권장합니다. -- **title** - - : [Safari를 제외한 모든 브라우저는 현재 이 매개변수를 무시](https://github.com/whatwg/html/issues/2174)하지만 향후 이 매개변수를 사용할 수 있습니다. 여기에 빈 문자열을 전달하면 향후 메서드 변경에 대비하여 안전할 것입니다. 또는 이동하려는 상태에 대한 짧은 제목을 전달할 수도 있습니다. -- **URL** - - : 새 기록 항목의 URL은 이 매개변수에 의해 지정됩니다. 브라우저는 `pushState()`를 호출한 후에는 이 URL을 로드하려고 시도하지 않지만, 사용자가 브라우저를 다시 시작한 후와 같이 나중에 URL을 로드하려고 시도할 수 있습니다. 새 URL은 절대 URL일 필요는 없으며, 상대 URL인 경우 현재 URL을 기준으로 확인됩니다. 새 URL은 현재 URL과 동일한 출처여야 합니다. 그렇지 않으면 `pushState()`가 예외를 던집니다. 이 매개변수는 선택 사항이며, 지정하지 않으면 문서의 현재 URL로 설정됩니다. +- 페이지의 일부만 업데이트해야 할 때마다 전체 페이지를 로드하는 것은 비효율적일 수 있습니다. +- 페이지 간 네비게이션 시 애플리케이션 상태를 유지하기 어렵습니다. -어떤 의미에서 `pushState()`를 호출하는 것은 현재 문서와 연결된 다른 히스토리 항목도 생성하고 활성화한다는 점에서 `window.location = "#foo"`를 설정하는 것과 유사합니다. +이러한 이유로 웹 애플리케이션에서 인기 있는 패턴은 {{Glossary("SPA", "싱글 페이지 애플리케이션")}}(SPA)이며, 이 경우 사이트는 단일 페이지로 구성되고 사용자가 링크를 클릭할 때 페이지는 다음과 같이 동작합니다. -하지만 `pushState()`에는 몇 가지 장점이 있습니다. +1. 새 페이지를 로드하는 기본 동작을 방지합니다. +2. {{domxref("Window/fetch", "Fetches", "", "nocode")}} 새로운 콘텐츠를 가져옵니다. +3. 새로운 콘텐츠로 페이지를 업데이트합니다. -- 새 URL은 현재 URL과 동일한 원본에 있는 모든 URL이 될 수 있습니다. 반면, `window.location`을 설정하면 해시만 수정하는 경우에는 동일한 {{ domxref("document") }}로 유지됩니다. -- 원하지 않는 경우 URL을 변경하지 않아도 됩니다. 반대로, `window.location = "#foo";`를 설정하면 현재 해시가 `#foo`가 아닌 경우에만 새 기록 항목이 생성됩니다. -- 임의의 데이터를 새 기록 항목에 연결할 수 있습니다. 해시 기반 접근 방식을 사용하면 모든 관련 데이터를 짧은 문자열로 인코딩해야 합니다. -- 만약 이후 `title` 브라우저에서 제목을 사용하는 경우, 해시와 무관하게 이 데이터를 활용할 수 있습니다. +다음의 예시를 살펴봅시다. -새 URL이 이전 URL과 해시만 다르더라도 `pushState()`는 이벤트를 발생시키지 않는다는 점에 유의해야 합니다. +```js +document.addEventListener("click", async (event) => { + const creature = event.target.getAttribute("data-creature"); + if (creature) { + // 새 페이지가 로드되지 않도록 방지 + event.preventDefault(); + try { + // 새로운 콘텐츠 가져오기 + const response = await fetch(`creatures/${creature}.json`); + const json = await response.json(); + // 새로운 콘텐츠로 페이지 업데이트 + displayContent(json); + } catch (err) { + console.error(err); + } + } +}); +``` -다른 문서에서는 `null` 네임스페이스 URI를 가진 요소를 생성합니다. +이 클릭 핸들러에서, 링크에 `"data-creature"` 데이터 속성이 포함되어 있다면, 해당 속성의 값을 사용하여 페이지의 새로운 콘텐츠를 포함하는 JSON 파일을 가져옵니다. -### replaceState() 메서드 +JSON 파일은 다음과 같을 수 있습니다. -`history.replaceState()`는 `history.pushState()`와 똑같이 작동하지만, `replaceState()`는 새 항목을 만드는 대신 현재 기록 항목을 수정한다는 점이 다릅니다. 이렇게 해도 전역 브라우저 기록에 새 항목이 생성되는 것을 막지는 못합니다. +```json +{ + "description": "대머리 독수리는 실제로 대머리가 아닙니다.", + "image": { + "src": "images/eagle.jpg", + "alt": "대머리 독수리" + }, + "name": "독수리" +} +``` -`replaceState()`는 특정 사용자 작업에 대한 응답으로 현재 기록 항목의 상태 개체 또는 URL을 업데이트하려는 경우에 특히 유용합니다. +`displayContent()` 함수는 JSON으로 페이지를 업데이트합니다. -### replaceState() 메서드 예제 +```js +// 새로운 콘텐츠으로 페이지 업데이트 +function displayContent(content) { + document.title = `생물: ${content.name}`; -`https://mozilla.org/foo.html`이 다음 JavaScript 코드를 실행한다고 가정합니다. + const description = document.querySelector("#description"); + description.textContent = content.description; -```js -const stateObj = { - foo: "bar", -}; -history.pushState(stateObj, "page 2", "bar.html"); + const photo = document.querySelector("#photo"); + photo.setAttribute("src", content.image.src); + photo.setAttribute("alt", content.image.alt); +} ``` -위의 두 줄에 대한 설명은 [pushState() 메서드 예제](<#pushState()_메서드_예제>) 섹션에서 확인할 수 있습니다. +문제는 이것이 브라우저의 "뒤로" 및 "앞으로" 버튼의 예상 동작을 깨뜨린다는 것입니다. + +사용자의 관점에서는 링크를 클릭하고 페이지가 업데이트되었으므로 새 페이지처럼 보입니다. 그런 다음 브라우저의 "뒤로" 버튼을 누르면 링크를 클릭하기 전 상태로 돌아가길 기대합니다. + +하지만 브라우저의 관점에서는 마지막 링크가 새 페이지를 로드하지 않았으므로 "뒤로" 버튼을 누르면 사용자가 SPA를 열기 전에 로드된 페이지로 이동하게 됩니다. -다음으로 `https://mozilla.org/bar.html`이 다음 JavaScript 코드를 실행한다고 가정합니다. +이것이 `pushState()`, `replaceState()`, 그리고 `popstate` 이벤트가 해결하는 문제입니다. 이들은 역사 항목을 합성하고, 현재 세션 기록 항목이 이러한 항목 중 하나로 변경될 때(예를 들어, 사용자가 "뒤로" 또는 "앞으로" 버튼을 눌렀을 때)를 알 수 있게 해줍니다. + +## `pushState()` 사용하기 + +위의 클릭 핸들러에 역사 항목을 추가할 수 있습니다. ```js -history.replaceState(stateObj, "page 3", "bar2.html"); +document.addEventListener("click", async (event) => { + const creature = event.target.getAttribute("data-creature"); + if (creature) { + event.preventDefault(); + try { + const response = await fetch(`creatures/${creature}.json`); + const json = await response.json(); + displayContent(json); + // 새로운 역사 항목을 추가합니다. + // 이는 새 페이지를 로드하는 것을 시뮬레이션합니다. + history.pushState(json, "", creature); + } catch (err) { + console.error(err); + } + } +}); ``` -이렇게 하면 URL 표시줄에 `https://mozilla.org/bar2.html`이 표시되지만 브라우저가 `bar2.html`를 로드하거나 `bar2.html`이 존재하는지 확인하지는 않습니다. +여기서 우리는 세 가지 인수로 `pushState()`를 호출하고 있습니다. + +- `json`: 방금 가져온 콘텐츠입니다. 이는 역사 항목과 함께 저장되며, 나중에 사용자가 A로 네비게이션할 때 `popstate` 이벤트 핸들러에 전달되는 인수의 {{domxref("PopStateEvent.state", "state")}} 속성으로 포함됩니다. +- `""`: 이는 레거시 사이트와의 하위 호환성을 위해 필요하며, 항상 빈 문자열이어야 합니다. +- `creature`: 이는 항목의 URL로 사용됩니다. 브라우저의 URL 표시줄에 표시되며, 페이지가 수행하는 모든 HTTP 요청에서 {{httpheader("Referer")}} 헤더의 값으로 사용됩니다. 이는 페이지와 {{Glossary("Same-origin policy", "동일 출처 정책")}}이어야 합니다. -이제 사용자가 `https://www.microsoft.com`로 이동한 다음 **뒤로가기**버튼을 클릭한다고 가정하겠습니다. 이 시점에서 URL 표시줄에 `https://mozilla.org/bar2.html`이 표시됩니다. 이제 사용자가 **뒤로가기**버튼을 다시 클릭하면 URL 표시줄에 `https://mozilla.org/foo.html`가 표시되고 `bar.html`이 완전히 우회됩니다. +## `popstate` 이벤트 사용하기 -### popstate 이벤트 +사용자가 다음과 같이 동작한다고 가정해 봅시다. -활성 기록 항목이 변경될 때마다 창에 `popstate` 이벤트가 전송됩니다. 활성화 중인 기록 항목이 {{DOMxRef("History.pushState","pushState")}} 호출에 의해 생성되었거나 {{DOMxRef("History.replaceState","replaceState")}} 호출의 영향을 받은 경우, `popstate` 이벤트의 `state` 속성에는 기록 항목의 상태 객체 복사본이 포함됩니다. +1. SPA에서 링크를 클릭하여 페이지를 업데이트하고 `pushState()`를 사용하여 역사 항목 A를 추가합니다. +2. SPA에서 다른 링크를 클릭하여 페이지를 업데이트하고 `pushState()`를 사용하여 역사 항목 B를 추가합니다. +3. "뒤로" 버튼을 누릅니다. -샘플 사용법은 {{domxref("Window/popstate_event", "popstate")}}에서 확인하시면 됩니다. +이제 새로운 현재 역사 항목은 A가 되므로, 브라우저는 `popstate` 이벤트를 발생시키고, 이벤트 핸들러 인수는 A로 네비게이션할 때 `pushState()`에 전달했던 JSON을 포함합니다. 이는 다음과 같은 이벤트 핸들러를 사용하여 올바른 콘텐츠를 복원할 수 있음을 의미합니다. + +```js +// 앞으로/뒤로 버튼 처리 +window.addEventListener("popstate", (event) => { + // 상태가 제공된 경우, "시뮬레이션된" 페이지가 있으며 현재 페이지를 업데이트합니다. + if (event.state) { + // 이전 페이지 로드를 시뮬레이션합니다. + displayContent(event.state); + } +}); +``` -### 현재 상태 읽기 +## `replaceState()` 사용하기 -페이지가 로드될 때 non-null 상태가 아닌 객체가 있을 수 있습니다. 예를 들어 페이지에서 상태 객체를 설정한 후 ({{DOMxRef("History.pushState","pushState()")}} 또는 {{DOMxRef("History.replaceState","replaceState()")}}사용)사용자가 브라우저를 재시작하는 경우 이런 일이 발생할 수 있습니다. 페이지가 다시 로드되면 페이지에 `onload` 이벤트가 수신되지만 `popstate` 이벤트는 수신되지 않습니다. 그러나 {{DOMxRef("History.state","history.state")}} 속성을 읽으면 `popstate`가 발생했을 때 얻을 수 있는 상태 객체를 다시 얻을 수 있습니다. +추가해야 할 한 가지가 더 있습니다. 사용자가 SPA를 로드할 때, 브라우저는 역사 항목을 추가합니다. 이 항목은 실제 페이지 로드였기 때문에 상태가 연관되어 있지 않습니다. 따라서 사용자가 다음과 같이 동작한다고 가정해 봅시다. -다음과 같이 {{DOMxRef("History.state","history.state")}} 속성을 사용하여 `popstate` 이벤트를 기다리지 않고 현재 기록 항목의 상태를 읽을 수 있습니다. +1. SPA를 로드합니다. 브라우저는 역사(History) 항목을 추가합니다. +2. SPA 내의 링크를 클릭하여 페이지를 업데이트하고 `pushState()`를 사용하여 역사 항목을 추가합니다. +3. "뒤로" 버튼을 누릅니다. + +이제 사용자는 SPA의 초기 상태로 돌아가길 원하지만, 이는 같은 문서 내에서의 네비게이션이기 때문에 페이지가 다시 로드되지 않으며, 초기 페이지의 역사 항목에는 상태가 없기 때문에 `popstate`를 사용하여 복원할 수 없습니다. + +해결책은 `replaceState()`를 사용하여 초기 페이지의 상태 객체를 설정하는 것입니다. 예를 들면 다음과 같습니다. ```js -const currentState = history.state; +// 페이지 로드 시 상태를 생성하고 현재 기록을 교체합니다. +const image = document.querySelector("#photo"); +const initialState = { + description: document.querySelector("#description").textContent, + image: { + src: image.getAttribute("src"), + alt: image.getAttribute("alt"), + }, + name: "홈", +}; +history.replaceState(initialState, "", document.location.href); ``` +페이지 로드 시, 사용자가 SPA의 시작 지점으로 돌아갔을 때 복원할 모든 페이지의 부분을 수집합니다. 이는 다른 네비게이션을 처리할 때 가져오는 JSON과 동일한 구조를 가지고 있습니다. 우리는 이 `initialState` 객체를 `replaceState()`에 전달하여 현재 역사 항목에 상태 객체를 효과적으로 추가합니다. + +사용자가 시작 지점으로 돌아가면, `popstate` 이벤트는 이 초기 상태를 포함하게 되며, 우리는 `displayContent()` 함수를 사용하여 페이지를 업데이트할 수 있습니다. + ## 같이 보기 - [History API](/ko/docs/Web/API/History_API) -- [History navigation 예제](/ko/docs/Web/API/History_API/Working_with_the_History_API) - {{domxref("window.history", "history")}} 전역 객체