페이지 수명 주기 API

브라우저 지원

  • Chrome: 68
  • Edge: 79.
  • Firefox: 지원되지 않음
  • Safari: 지원되지 않음

오늘날의 최신 브라우저는 시스템 리소스가 제약될 때 페이지를 일시중지하거나 완전히 삭제하는 경우가 있습니다. 향후 브라우저는 전원과 메모리를 더 적게 소비하기 위해 이를 사전에 처리하려고 합니다. Page Lifecycle API는 페이지가 사용자 환경에 영향을 주지 않고 이러한 브라우저 개입을 안전하게 처리할 수 있도록 수명 주기 후크를 제공합니다. API를 살펴보고 애플리케이션에 이러한 기능을 구현해야 하는지 확인하세요.

배경

애플리케이션 수명 주기는 첨단 운영체제에서 리소스를 관리하는 핵심적인 방식입니다. Android, iOS, 최신 Windows 버전에서는 OS에서 언제든지 앱을 시작하고 중지할 수 있습니다. 이를 통해 이러한 플랫폼에서는 리소스를 간소화하고 사용자에게 가장 이득이 되는 곳에 재할당할 수 있습니다.

웹에서는 지금까지 이러한 수명 주기의 개념이 적용된 적이 없었기에, 앱이 무기한으로 활성 상태로 유지될 수 있습니다. 웹페이지를 다량으로 실행하면 메모리, CPU, 배터리, 네트워크와 같은 중요한 시스템 리소스가 초과 구독되어 최종 사용자 환경이 나빠질 수 있습니다.

웹 플랫폼에는 오래전부터 load, unload, visibilitychange와 같은 수명 주기 상태와 관련된 이벤트가 있었지만 이러한 이벤트를 통해 개발자는 사용자가 시작한 수명 주기 상태 변경에 응답할 수만 있습니다. 웹이 저전력 기기에서 안정적으로 작동하고 모든 플랫폼에서 일반적으로 리소스를 더 잘 활용하려면 브라우저에 시스템 리소스를 사전에 재활용하고 재할당하는 방법이 필요합니다.

사실 오늘날 브라우저는 이미 백그라운드 탭의 페이지에 대해 리소스를 보존하기 위한 적극적인 조치를 취하고 있으며, 많은 브라우저(특히 Chrome)는 전반적인 리소스 사용량을 줄이기 위해 이러한 조치를 더 많이 취하고자 합니다.

문제는 개발자가 이러한 유형의 시스템 시작 개입에 대비하거나 개입이 진행되고 있다는 사실을 알 방법이 없다는 것입니다. 즉, 브라우저는 보수적으로 작동해야 하며 그렇지 않으면 웹페이지가 손상될 수 있습니다.

Page Lifecycle API는 다음과 같은 방식으로 이 문제를 해결하려고 시도합니다.

  • 웹에서 수명 주기 상태의 개념을 도입하고 표준화합니다.
  • 브라우저가 숨겨진 탭 또는 비활성 탭에서 사용할 수 있는 리소스를 제한할 수 있는 새로운 시스템 시작 상태를 정의합니다.
  • 웹 개발자가 이러한 새로운 시스템 시작 상태와의 전환에 응답할 수 있는 새 API 및 이벤트를 만듭니다.

이 솔루션은 웹 개발자가 시스템 개입에 탄력적으로 반응하는 애플리케이션을 빌드하는 데 필요한 예측 가능성을 제공하며, 브라우저가 시스템 리소스를 더욱 적극적으로 최적화하여 궁극적으로 모든 웹 사용자에게 이점을 제공할 수 있도록 합니다.

이 게시물의 나머지 부분에서는 새로운 페이지 수명 주기 기능을 소개하고 이러한 기능이 기존의 모든 웹 플랫폼 상태 및 이벤트와 어떤 관련이 있는지 살펴봅니다. 또한 개발자가 각 상태에서 수행해야 하는 작업 유형과 수행해서는 안 되는 작업 유형에 관한 권장사항과 권장사항을 제공합니다.

페이지 수명 주기 상태 및 이벤트 개요

모든 페이지 수명 주기 상태는 개별적이고 상호 배타적입니다. 즉, 페이지는 한 번에 하나의 상태에만 있을 수 있습니다. 또한 페이지의 수명 주기 상태에 대한 대부분의 변경사항은 일반적으로 DOM 이벤트를 통해 관찰할 수 있습니다(예외는 각 상태에 관한 개발자 권장사항 참고).

페이지 수명 주기 상태와 페이지 수명 주기 상태 간의 전환을 알리는 이벤트를 가장 쉽게 설명하는 방법은 다이어그램을 사용하는 것입니다.

이 문서 전반에 설명된 상태 및 이벤트 흐름을 시각적으로 나타낸 그림입니다.
Page Lifecycle API 상태 및 이벤트 흐름

다음 표에는 각 상태에 관한 자세한 설명이 나와 있습니다. 또한 그 전후에 올 수 있는 상태와 개발자가 변경사항을 관찰하는 데 사용할 수 있는 이벤트도 나열합니다.

설명
활성

페이지가 표시되고 입력 포커스가 있는 경우 페이지는 활성 상태입니다.

가능한 이전 상태:
수동 (focus 이벤트를 통해)
동결됨 (resume 이벤트, pageshow 이벤트를 통해)

가능한 다음 상태:
수동 ( blur 이벤트를 통해)

수동적

페이지가 표시되고 입력 포커스가 없는 경우 수동 상태입니다.

이전 상태:
활성 (blur 이벤트를 통해)
숨김 (visibilitychange 이벤트를 통해)
중지됨 (resume 이벤트, pageshow 이벤트를 통해)

다음 상태가 가능:
활성 (focus 이벤트를 통해)
숨김 ( visibilitychange 이벤트를 통해)

숨김

페이지가 표시되지 않고 고정, 삭제 또는 해지되지 않은 경우 숨김 상태가 됩니다.

가능한 이전 상태:
수동 ( visibilitychange 이벤트를 통해)
동결됨 (resume 이벤트, pageshow 이벤트를 통해)

가능한 다음 상태:
수동 ( visibilitychange 이벤트를 통해)
동결됨 (freeze 이벤트를 통해)
삭제됨 (이벤트가 실행되지 않음)
종료됨 (이벤트가 실행되지 않음)

정지됨

고정 상태에서 브라우저는 페이지가 고정 해제될 때까지 페이지의 태스크 큐에 있는 고정 가능한 작업의 실행을 정지합니다. 즉, JavaScript 타이머 및 가져오기 콜백과 같은 항목이 실행되지 않습니다. 이미 실행 중인 태스크는 완료될 수 있지만(가장 중요한 것은 freeze 콜백) 할 수 있는 작업과 실행할 수 있는 시간은 제한될 수 있습니다.

브라우저는 CPU/배터리/데이터 사용량을 보존하기 위한 방법으로 페이지를 고정하며, 전체 페이지를 새로고침할 필요 없이 더 빠른 뒤로/앞으로 탐색을 사용 설정하기 위한 방편이기도 합니다.

가능한 이전 상태:
숨김 (freeze 이벤트를 통해)

가능한 다음 상태:
활성 (resume 이벤트 사용 후 pageshow 이벤트 사용)
수동 resume 이벤트로 이동한 다음 pageshow 이벤트를 통해pageshow 실행된 이벤트2이벤트2}이벤트를 통해 (숨겨진 이벤트 없음)
()

resume

종료됨

페이지가 브라우저에 의해 메모리에서 언로드되고 삭제되기 시작하면 종료됨 상태가 됩니다. 이 상태에서는 새 작업을 시작할 수 없으며 진행 중인 작업이 너무 오래 실행되면 종료될 수 있습니다.

가능한 이전 상태:
숨김 (pagehide 이벤트를 통해)

다음 상태:
없음

삭제됨

페이지가 리소스를 절약하기 위해 브라우저에서 로드 취소되면 삭제됨 상태가 됩니다. 이 상태에서는 작업, 이벤트 콜백 또는 JavaScript를 실행할 수 없습니다. 일반적으로 삭제는 새 프로세스를 시작할 수 없는 리소스 제약 조건 하에서 발생하기 때문입니다.

삭제됨 상태에서는 일반적으로 페이지가 사라진 경우에도 탭 자체(탭 제목 및 파비콘 포함)가 사용자에게 표시됩니다.

가능한 이전 상태:
숨김 (이벤트가 실행되지 않음)
고정됨 (이벤트가 실행되지 않음)

다음 상태:
없음

이벤트

브라우저는 많은 이벤트를 전달하지만 그중 소수만 페이지 수명 주기 상태의 가능한 변경사항을 신호합니다. 다음 표에는 수명 주기와 관련된 모든 이벤트가 요약되어 있으며, 전환할 수 있는 상태가 나와 있습니다.

이름 세부정보
focus

DOM 요소가 포커스를 받았습니다.

참고: focus 이벤트가 반드시 상태 변경을 알리는 것은 아닙니다. 페이지에 이전에 입력 포커스가 없었던 경우에만 상태 변경을 신호합니다.

이전 상태:
passive

가능한 현재 상태:
active(활성)

blur

DOM 요소가 포커스를 잃었습니다.

참고: blur 이벤트가 반드시 상태 변경을 나타내는 것은 아닙니다. 페이지에 더 이상 입력 포커스가 없는 경우 (즉, 페이지가 한 요소에서 다른 요소로 포커스를 전환하지 않는 경우)에만 상태 변경을 알립니다.

이전 상태:
active(활성)

가능한 현재 상태:
수동

visibilitychange

문서의 visibilityState 값이 변경되었습니다. 이는 사용자가 새 페이지로 이동하거나, 탭을 전환하거나, 탭을 닫거나, 브라우저를 최소화하거나 닫거나, 모바일 운영체제에서 앱을 전환할 때 발생할 수 있습니다.

가능한 이전 상태:
수동
숨김

가능한 현재 상태:
수동
숨김

freeze *

페이지가 방금 동결되었습니다. 페이지의 태스크 큐에 있는 중지 가능한 태스크는 시작되지 않습니다.

이전 상태:
숨김

가능한 현재 상태:
frozen

resume *

브라우저가 중지된 페이지를 다시 시작했습니다.

가능한 이전 상태:
frozen

가능한 현재 상태:
활성 (다음에 pageshow 이벤트가 오는 경우)
수동 (다음에 pageshow 이벤트가 오는 경우)
숨김

pageshow

세션 기록 항목을 이동하고 있습니다.

이는 완전히 새로운 페이지 로드일 수도 있고 뒤로-앞으로 캐시에서 가져온 페이지일 수도 있습니다. 페이지가 뒤로/앞으로 캐시에서 가져온 경우 이벤트의 persisted 속성은 true이고, 그렇지 않으면 false입니다.

가능한 이전 상태:
frozen (resume 이벤트도 실행됨)

가능한 현재 상태:
활성
수동
숨김

pagehide

세션 기록 항목을 순회하고 있습니다.

사용자가 다른 페이지로 이동 중이고 브라우저가 나중에 재사용할 수 있도록 현재 페이지를 뒤로/앞으로 캐시에 추가할 수 있는 경우 이벤트의 persisted 속성은 true입니다. true이면 페이지가 중지된 상태로 전환되고, 그렇지 않으면 종료된 상태로 전환됩니다.

이전 상태:
숨김

가능한 현재 상태:
동결됨 (event.persisted은 true이고 freeze 이벤트가 뒤에 있음)
종료됨 (event.persisted은 false이고 unload 이벤트가 뒤에 있음)

beforeunload

창, 문서, 리소스가 로드 취소됩니다. 문서는 계속 표시되며 이 시점에서 이벤트는 여전히 취소할 수 있습니다.

중요: beforeunload 이벤트는 저장되지 않은 변경사항을 사용자에게 알리는 데만 사용해야 합니다. 변경사항을 저장하면 이벤트가 삭제됩니다. 경우에 따라 성능이 저하될 수 있으므로 무조건 페이지에 추가해서는 안 됩니다. 자세한 내용은 기존 API 섹션을 참고하세요.

이전 상태:
숨김

가능한 현재 상태:
종료됨

unload

페이지가 언로드되고 있습니다.

경고: unload 이벤트는 신뢰할 수 없고 경우에 따라 성능이 저하될 수 있으므로 사용하지 않는 것이 좋습니다. 자세한 내용은 기존 API 섹션을 참고하세요.

이전 상태:
숨김

가능한 현재 상태:
terminated

* 페이지 수명 주기 API에 의해 정의된 새 이벤트를 나타냅니다.

Chrome 68에 추가된 새로운 기능

이전 차트에는 사용자 시작이 아닌 시스템 시작인 두 가지 상태, 동결됨삭제됨이 표시됩니다. 앞서 언급했듯이 오늘날 브라우저는 이미 숨겨진 탭을 임의로 정지하고 삭제하는 경우가 있지만 개발자는 언제 이러한 일이 발생하는지 알 수 없습니다.

이제 Chrome 68에서 개발자는 document에서 freezeresume 이벤트를 수신 대기하여 숨겨진 탭이 고정되고 고정 해제되는 시점을 관찰할 수 있습니다.

document.addEventListener('freeze', (event) => {
  // The page is now frozen.
});

document.addEventListener('resume', (event) => {
  // The page has been unfrozen.
});

Chrome 68부터 document 객체에 데스크톱 Chrome의 wasDiscarded 속성이 포함됩니다(이 문제에서 Android 지원 추적). 숨겨진 탭에 있는 동안 페이지가 삭제되었는지 확인하려면 페이지 로드 시 이 속성의 값을 검사하면 됩니다. 참고로 삭제된 페이지를 다시 사용하려면 새로고침해야 합니다.

if (document.wasDiscarded) {
  // Page was previously discarded by the browser while in a hidden tab.
}

freezeresume 이벤트에서 중요한 작업과 페이지가 삭제되는 것을 처리하고 준비하는 방법에 관한 도움말은 각 상태에 관한 개발자 권장사항을 참고하세요.

다음 섹션에서는 이러한 새로운 기능이 기존 웹 플랫폼 상태 및 이벤트에 어떻게 적용되는지 간략히 설명합니다.

코드에서 페이지 수명 주기 상태를 관찰하는 방법

활성, 수동, 숨김 상태에서는 기존 웹 플랫폼 API에서 현재 페이지 수명 주기 상태를 결정하는 JavaScript 코드를 실행할 수 있습니다.

const getState = () => {
  if (document.visibilityState === 'hidden') {
    return 'hidden';
  }
  if (document.hasFocus()) {
    return 'active';
  }
  return 'passive';
};

반면 동결된 상태와 종료된 상태는 상태가 변경될 때 각 이벤트 리스너(freezepagehide)에서만 감지할 수 있습니다.

상태 변경사항을 관찰하는 방법

이전에 정의된 getState() 함수를 기반으로 다음 코드를 사용하여 모든 페이지 수명 주기 상태 변경사항을 관찰할 수 있습니다.

// Stores the initial state using the `getState()` function (defined above).
let state = getState();

// Accepts a next state and, if there's been a state change, logs the
// change to the console. It also updates the `state` value defined above.
const logStateChange = (nextState) => {
  const prevState = state;
  if (nextState !== prevState) {
    console.log(`State change: ${prevState} >>> ${nextState}`);
    state = nextState;
  }
};

// Options used for all event listeners.
const opts = {capture: true};

// These lifecycle events can all use the same listener to observe state
// changes (they call the `getState()` function to determine the next state).
['pageshow', 'focus', 'blur', 'visibilitychange', 'resume'].forEach((type) => {
  window.addEventListener(type, () => logStateChange(getState()), opts);
});

// The next two listeners, on the other hand, can determine the next
// state from the event itself.
window.addEventListener('freeze', () => {
  // In the freeze event, the next state is always frozen.
  logStateChange('frozen');
}, opts);

window.addEventListener('pagehide', (event) => {
  // If the event's persisted property is `true` the page is about
  // to enter the back/forward cache, which is also in the frozen state.
  // If the event's persisted property is not `true` the page is
  // about to be unloaded.
  logStateChange(event.persisted ? 'frozen' : 'terminated');
}, opts);

이 코드는 다음 세 가지 작업을 실행합니다.

  • getState() 함수를 사용하여 초기 상태를 설정합니다.
  • 다음 상태를 허용하고 변경사항이 있는 경우 상태 변경사항을 콘솔에 로깅하는 함수를 정의합니다.
  • 필요한 모든 수명 주기 이벤트에 캡처 이벤트 리스너를 추가합니다. 그러면 logStateChange()가 호출되어 다음 상태를 전달합니다.

이 코드에서 주목할 점은 모든 이벤트 리스너가 window에 추가되고 모두 {capture: true}를 전달한다는 것입니다. 여기에는 다음과 같은 이유가 있습니다.

  • 모든 페이지 수명 주기 이벤트의 타겟이 동일한 것은 아닙니다. pagehidepageshowwindow에서 실행되고, visibilitychange, freeze, resumedocument에서 실행되며, focusblur는 각 DOM 요소에서 실행됩니다.
  • 이러한 이벤트는 대부분 버블이 없습니다. 즉, 캡처하지 않는 이벤트 리스너를 공통 상위 요소에 추가하여 모두 관찰할 수 없습니다.
  • 캡처 단계는 타겟 또는 버블 단계 전에 실행되므로 여기에 리스너를 추가하면 다른 코드가 리스너를 취소하기 전에 실행할 수 있습니다.

각 상태에 대한 개발자 권장사항

개발자가 페이지 수명 주기 상태를 이해하고 그리고 코드에서 관찰하는 방법을 모두 아는 것이 중요합니다. 수행해야 할 (그리고 하지 않아야 하는) 작업 유형은 주로 페이지의 상태에 따라 다르기 때문입니다.

예를 들어 페이지가 숨겨진 상태인 경우 사용자에게 일시적인 알림을 표시하는 것은 분명히 적절하지 않습니다. 이 예는 꽤 분명하지만, 그다지 분명하지 않아 열거할 가치가 있는 다른 권장사항도 있습니다.

개발자 권장사항
Active

활성 상태는 사용자에게 가장 중요한 시간이므로 페이지가 사용자 입력에 반응하는 데 가장 중요한 시간입니다.

기본 스레드를 차단할 수 있는 UI 외 작업은 유휴 시간으로 우선순위를 낮추거나 웹 워커로 오프로드해야 합니다.

Passive

수동 상태에서 사용자는 페이지와 상호작용하지 않지만 페이지를 계속 볼 수 있습니다. 즉, UI 업데이트 및 애니메이션은 여전히 원활해야 하지만 이러한 업데이트가 발생하는 시점은 덜 중요합니다.

페이지가 활성에서 비활성으로 변경되면 저장되지 않은 애플리케이션 상태를 유지하는 것이 좋습니다.

Hidden

페이지가 수동에서 숨김으로 변경되면 페이지가 새로고침될 때까지 사용자가 다시 상호작용하지 않을 수 있습니다.

hidden으로의 전환은 개발자가 안정적으로 관찰할 수 있는 마지막 상태 변경이기도 합니다. 특히 모바일의 경우 사용자가 탭이나 브라우저 앱 자체를 닫을 수 있고 beforeunload, pagehide, unload 이벤트가 실행되지 않기 때문에 이러한 상태가 유지됩니다.

즉, hidden 상태를 사용자 세션의 종료 가능성이 높은 상태로 취급해야 합니다. 즉, 저장되지 않은 애플리케이션 상태를 유지하고 전송되지 않은 분석 데이터를 전송합니다.

또한 UI 업데이트는 사용자에게 표시되지 않으므로 업데이트도 중지해야 하며, 사용자가 백그라운드에서 실행하지 않기를 원하는 작업은 모두 중지해야 합니다.

Frozen

동결된 상태에서는 작업 대기열동결 가능한 작업이 페이지가 동결 해제될 때까지 일시중지됩니다. 동결 해제가 발생하지 않을 수도 있습니다(예: 페이지가 삭제된 경우).

즉, 페이지가 숨김에서 고정으로 변경되면 타이머를 중지하거나, 고정된 경우 같은 출처의 다른 열린 탭에 영향을 줄 수 있거나, 브라우저에서 페이지를 뒤로-앞으로 캐시에 저장하는 기능에 영향을 줄 수 있는 연결을 해체해야 합니다.

특히 다음 작업을 수행하는 것이 중요합니다.

  • 열려 있는 모든 IndexedDB 연결을 닫습니다.
  • 열려 있는 BroadcastChannel 연결을 닫습니다.
  • 활성 WebRTC 연결을 닫습니다.
  • 네트워크 폴링을 중지하거나 열려 있는 WebSocket 연결을 닫습니다.
  • 보류된 웹 잠금을 해제합니다.

또한 페이지를 삭제하고 나중에 다시 로드할 경우 복원하려는 모든 동적 뷰 상태 (예: 무한 목록 뷰의 스크롤 위치)를 sessionStorage (또는 commit()를 통한 IndexedDB)로 유지해야 합니다.

페이지가 고정에서 숨김으로 전환되면 닫힌 연결을 다시 열거나 페이지가 처음 고정되었을 때 중지한 폴링을 다시 시작할 수 있습니다.

Terminated

일반적으로 페이지가 terminated 상태로 전환될 때 취해야 할 별도의 조치는 없습니다.

사용자 작업의 결과로 로드 취소되는 페이지는 항상 종료됨 상태로 전환되기 전에 숨김 상태를 거치므로 숨김 상태에서 세션 종료 로직 (예: 애플리케이션 상태 유지 및 애널리틱스에 보고)을 실행해야 합니다.

또한 (숨김 상태 권장사항에 언급된 대로) 개발자는 종료됨 상태로의 전환이 많은 경우(특히 모바일에서) 안정적으로 감지되지 않을 수 있다는 점을 인식해야 합니다. 따라서 종료 이벤트(예: beforeunload, pagehide, unload)에 종속된 개발자는 데이터를 손실할 가능성이 높습니다.

Discarded

페이지가 삭제될 때 개발자는 삭제됨 상태를 관찰할 수 없습니다. 이는 일반적으로 페이지가 리소스 제약조건으로 인해 삭제되며, 삭제 이벤트에 대한 응답으로 스크립트를 실행하기 위해 페이지를 동결 해제하는 것은 대부분의 경우 불가능하기 때문입니다.

따라서 숨김에서 고정으로 변경될 때 삭제될 가능성에 대비한 다음 document.wasDiscarded를 확인하여 페이지 로드 시 삭제된 페이지의 복원에 대응할 수 있습니다.

다시 한번 말씀드리지만 수명 주기 이벤트의 안정성과 순서는 모든 브라우저에서 일관되게 구현되지 않으므로 표의 조언을 따르는 가장 쉬운 방법은 PageLifecycle.js를 사용하는 것입니다.

사용하지 말아야 할 기존 수명 주기 API

다음 이벤트는 가능하면 피해야 합니다.

언로드 이벤트

많은 개발자가 unload 이벤트를 보장된 콜백으로 취급하고 세션 종료 신호로 사용하여 상태를 저장하고 분석 데이터를 전송하지만, 특히 모바일에서는 이렇게 하는 것이 매우 신뢰할 수 없습니다. unload 이벤트는 대부분의 일반적인 로드 취소 상황에서는 실행되지 않습니다(예: 모바일의 탭 전환기에서 탭을 닫거나 앱 전환기에서 브라우저 앱을 닫는 경우).

따라서 항상 visibilitychange 이벤트를 사용하여 세션이 종료되는 시점을 확인하고 숨겨진 상태를 앱 및 사용자 데이터를 저장할 수 있는 마지막 안정적인 시간으로 간주하는 것이 좋습니다.

또한 (onunload 또는 addEventListener()를 통해) 등록된 unload 이벤트 핸들러가 있는 것만으로는 브라우저에서 페이지를 뒤로-앞으로 캐시에 넣어 더 빠른 뒤로-앞으로 로드를 사용하지 못할 수 있습니다.

모든 최신 브라우저에서는 항상 unload 이벤트 대신 pagehide 이벤트를 사용하여 가능한 페이지 언로드 (종료된 상태)를 감지하는 것이 좋습니다. Internet Explorer 버전 10 이하를 지원해야 하는 경우 pagehide 이벤트를 기능 감지하고 브라우저에서 pagehide를 지원하지 않는 경우에만 unload를 사용해야 합니다.

const terminationEvent = 'onpagehide' in self ? 'pagehide' : 'unload';

window.addEventListener(terminationEvent, (event) => {
  // Note: if the browser is able to cache the page, `event.persisted`
  // is `true`, and the state is frozen rather than terminated.
});

beforeunload 이벤트

beforeunload 이벤트에는 unload 이벤트와 유사한 문제가 있습니다. 즉, 이전에는 beforeunload 이벤트가 있으면 페이지에서 뒤로-앞으로 캐시를 사용하지 못할 수 있습니다. 최신 브라우저에는 이러한 제한이 없습니다. 일부 브라우저는 페이지를 뒤로/앞으로 캐시로 이동하려고 할 때 beforeunload 이벤트를 실행하지 않으므로 이 이벤트는 세션 종료 신호로 신뢰할 수 없습니다. 또한 일부 브라우저(예: Chrome)에서는 beforeunload 이벤트가 실행되기 전에 페이지에서 사용자 상호작용이 필요하므로 안정성에 더 큰 영향을 미칩니다.

beforeunloadunload의 한 가지 차이점은 beforeunload의 합법적인 용도가 있다는 점입니다. 예를 들어 페이지를 계속 언로드하면 저장하지 않은 변경사항이 삭제된다고 사용자에게 경고하려는 경우

beforeunload를 사용하는 데는 타당한 이유가 있으므로 사용자가 저장하지 않은 변경사항이 있을 때만 beforeunload 리스너를 추가하고 저장된 후 즉시 삭제하는 것이 좋습니다.

즉, 다음과 같이 하지 마세요. beforeunload 리스너가 무조건 추가되기 때문입니다.

addEventListener('beforeunload', (event) => {
  // A function that returns `true` if the page has unsaved changes.
  if (pageHasUnsavedChanges()) {
    event.preventDefault();

    // Legacy support for older browsers.
    return (event.returnValue = true);
  }
});

beforeunload 리스너를 필요할 때만 추가하고 필요하지 않을 때는 삭제하므로 다음과 같이 하세요.

const beforeUnloadListener = (event) => {
  event.preventDefault();
  
  // Legacy support for older browsers.
  return (event.returnValue = true);
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  removeEventListener('beforeunload', beforeUnloadListener);
});

FAQ

'로드 중' 상태가 없는 이유는 무엇인가요?

Page Lifecycle API는 상태를 불연속적이고 상호 배타적인 상태를 정의합니다. 페이지는 활성, 수동 또는 숨겨진 상태로 로드될 수 있으며 로드가 완료되기 전에 상태를 변경하거나 종료될 수도 있으므로 이 패러다임 내에서 별도의 로드 상태는 의미가 없습니다.

페이지가 숨겨져 있을 때 중요한 작업을 실행합니다. 페이지가 정지되거나 삭제되지 않도록 하려면 어떻게 해야 하나요?

웹페이지가 숨겨진 상태에서 실행되는 동안 정지되어서는 안 되는 합리적인 이유가 많이 있습니다. 가장 명확한 예는 음악을 재생하는 앱입니다.

제출되지 않은 사용자 입력이 포함된 양식이 있거나 페이지가 언로드될 때 경고하는 beforeunload 핸들러가 있는 경우와 같이 Chrome에서 페이지를 삭제하는 것이 위험한 경우도 있습니다.

당분간 Chrome은 페이지를 삭제할 때 보수적으로 접근하여 사용자에게 영향을 미치지 않을 것이라고 확신할 때만 삭제할 것입니다. 예를 들어 숨겨진 상태에서 다음 중 하나를 실행하는 것으로 확인된 페이지는 극심한 리소스 제약이 없는 한 삭제되지 않습니다.

  • 오디오 재생 중
  • WebRTC 사용
  • 테이블 제목 또는 favicon 업데이트
  • 알림 표시
  • 푸시 알림 보내기

탭을 안전하게 고정 또는 삭제할 수 있는지 확인하는 데 사용되는 현재 목록 기능은 Chrome의 고정 및 삭제를 위한 휴리스틱을 참고하세요.

뒤로-앞으로 캐시란 무엇인가요?

뒤로/앞으로 캐시는 일부 브라우저에서 구현하여 뒤로 및 앞으로 버튼을 더 빠르게 사용할 수 있도록 하는 탐색 최적화를 설명하는 데 사용되는 용어입니다.

이러한 브라우저는 사용자가 페이지에서 나가면 해당 페이지의 버전을 동결하여 사용자가 뒤로 또는 앞으로 버튼을 사용하여 다시 탐색할 때 빠르게 다시 시작할 수 있도록 합니다. unload 이벤트 핸들러를 추가하면 이 최적화가 불가능해집니다.

모든 의도와 목적상 이 동결은 CPU/배터리를 절약하기 위해 브라우저가 실행하는 동결과 기능적으로 동일합니다. 따라서 동결됨 수명 주기 상태의 일부로 간주됩니다.

고정 또는 종료된 상태에서 비동기 API를 실행할 수 없는 경우 데이터를 IndexedDB에 저장하려면 어떻게 해야 하나요?

고정 및 종료된 상태에서 페이지의 태스크 큐에 있는 고정 가능한 태스크가 정지됩니다. 즉, IndexedDB와 같은 비동기 및 콜백 기반 API를 안정적으로 사용할 수 없습니다.

향후 IDBTransactioncommit() 메서드를 추가할 예정입니다. 이를 통해 개발자는 콜백이 필요 없는 사실상 쓰기 전용 트랜잭션을 실행할 수 있습니다. 즉, 개발자가 IndexedDB에 데이터를 쓰고 있을 뿐 읽기와 쓰기로 구성된 복잡한 트랜잭션을 실행하고 있지 않은 경우 IndexedDB 데이터베이스가 이미 열려 있다고 가정하면 작업 큐가 일시중지되기 전에 commit() 메서드가 완료될 수 있습니다.

그러나 지금 작동해야 하는 코드의 경우 개발자에게는 다음 두 가지 옵션이 있습니다.

  • 세션 스토리지 사용: 세션 스토리지는 동기식이며 페이지 삭제 전후에 유지됩니다.
  • 서비스 워커에서 IndexedDB 사용: 서비스 워커는 페이지가 종료되거나 삭제된 후 IndexedDB에 데이터를 저장할 수 있습니다. freeze 또는 pagehide 이벤트 리스너에서 postMessage()를 통해 서비스 워커에 데이터를 전송할 수 있으며 서비스 워커는 데이터 저장을 처리할 수 있습니다.

정지 및 삭제된 상태에서 앱 테스트

앱이 정지 및 삭제된 상태에서 어떻게 동작하는지 테스트하려면 chrome://discards로 이동하여 열려 있는 탭을 실제로 정지하거나 삭제하면 됩니다.

Chrome 삭제 UI
Chrome에서 UI 삭제

이렇게 하면 페이지가 삭제된 후 새로고침될 때 freezeresume 이벤트와 document.wasDiscarded 플래그를 올바르게 처리할 수 있습니다.

요약

사용자 기기의 시스템 리소스를 고려하려는 개발자는 페이지 수명 주기 상태를 염두에 두고 앱을 빌드해야 합니다. 사용자가 예상하지 못한 상황에서 웹페이지가 과도한 시스템 리소스를 사용하지 않는 것이 중요합니다.

개발자가 새 Page Lifecycle API를 구현할수록 브라우저가 사용되지 않는 페이지를 동결하고 삭제하는 것이 더 안전해집니다. 즉, 브라우저가 메모리, CPU, 배터리, 네트워크 리소스를 적게 소비하므로 사용자에게 유리합니다.