페이지 수명 주기 API

브라우저 지원

  • 68
  • 79
  • x
  • x

오늘날의 최신 브라우저는 시스템 리소스가 제한되면 페이지를 정지하거나 완전히 삭제하는 경우가 있습니다. 앞으로 브라우저는 이 작업을 사전에 실행하여 전력과 메모리를 덜 소비합니다. 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 이벤트를 통해, 이후 이벤트를 통해){/22pageshow

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

숨김

페이지가 표시되지 않고 정지, 삭제, 종료되지 않은 경우 숨김 상태입니다.

가능한 이전 상태:
수동 ( visibilitychange 이벤트를 통해)
고정 (resume 이벤트 후 pageshow 이벤트를 통해)

가능한 다음 상태:
수동 ( visibilitychange 이벤트를 통해)
고정 (freeze 이벤트를 통해)
삭제됨 (실행된 이벤트 없음)
종료됨 (실행된 이벤트 없음)

고정

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

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

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

가능한 다음 상태:
활성 (resume 이벤트 후, pageshow 이벤트를 통해)
수동 resume 이벤트를 통해, 그런 다음 pageshow 이벤트를 통해)
숨긴 이벤트
숨긴 이벤트를 숨김 {2resume}
{2resume} 숨긴 {2resume

종료됨

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

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

가능한 다음 상태:
없음

삭제됨

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

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

가능한 이전 상태:
숨김 (실행된 이벤트 없음)
고정 (실행된 이벤트 없음)

가능한 다음 상태:
없음

이벤트

브라우저는 많은 이벤트를 전송하지만 일부 이벤트만 페이지 수명 주기 상태의 변경 가능성을 알립니다. 다음 표에는 수명 주기와 관련된 모든 이벤트와 이벤트가 전환되거나 전환될 수 있는 상태가 나열되어 있습니다.

이름 세부정보
focus

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

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

가능한 이전 상태:
수동

가능한 현재 상태:
활성

blur

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

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

가능한 이전 상태:
활성

가능한 현재 상태:
수동

visibilitychange

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

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

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

freeze *

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

가능한 이전 상태:
숨김

가능한 현재 상태:
고정

resume *

브라우저에서 정지된 페이지를 재개했습니다.

가능한 이전 상태:
고정

가능한 현재 상태:
활성 ( pageshow 이벤트가 발생한 경우)
수동 ( pageshow 이벤트가 뒤따르는 경우)
숨김

pageshow

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

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

가능한 이전 상태:
고정됨 (resume 이벤트도 실행되었을 수 있음)

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

pagehide

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

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

가능한 이전 상태:
숨김

가능한 현재 상태:
고정됨 (event.persisted은 true, freeze 이벤트 후)
종료됨 (event.persisted는 false, unload 이벤트 후)

beforeunload

창, 문서 및 리소스가 곧 로드 해제됩니다. 이 시점에서 문서가 계속 표시되고 이벤트를 취소할 수 있습니다.

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

가능한 이전 상태:
숨김

가능한 현재 상태:
종료됨

unload

페이지를 로드 취소하는 중입니다.

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

가능한 이전 상태:
숨김

가능한 현재 상태:
종료됨

* Page Lifecycle 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부터 이제 데스크톱 Chrome의 document 객체에 wasDiscarded 속성이 포함됩니다. Android 지원은 이 문제에서 추적 중입니다. 숨겨진 탭에 있는 동안 페이지가 삭제되었는지 확인하려면 페이지 로드 시 이 속성 값을 검사하면 됩니다 (참고: 다시 사용하려면 삭제된 페이지를 새로고침해야 함).

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

freezeresume 이벤트에서 해야 할 일과 페이지 삭제 처리 및 준비 방법에 관한 조언은 각 상태에 관한 개발자 권장사항을 참고하세요.

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

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

활성, 수동, 숨김 상태에서는 기존 웹 플랫폼 API에서 현재 페이지 수명 주기 상태를 확인하는 자바스크립트 코드를 실행할 수 있습니다.

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 이벤트가 실행되지 않으므로 모바일에서는 특히 그렇습니다.

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

또한 UI 업데이트 (사용자에게 표시되지 않으므로)도 중지해야 하며 사용자가 백그라운드에서 실행하고 싶지 않은 작업은 중지해야 합니다.

Frozen

고정 상태에서는 태스크 큐 고정 가능한 태스크는 페이지가 고정 해제될 때까지 정지되며, 고정 해제는 절대 발생하지 않습니다 (예: 페이지가 삭제되는 경우).

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

특히 다음 사항이 중요합니다.

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

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

Terminated

일반적으로 페이지가 terminated 상태로 전환될 때 아무런 조치도 취할 필요가 없습니다.

사용자 작업으로 인해 로드 해제되는 페이지는 항상 종료 상태로 전환되기 전에 숨김 상태를 거치므로 숨김 상태에서는 세션 종료 로직 (예: 애플리케이션 상태 유지 및 분석에 보고)이 실행됩니다.

또한 숨김 상태 권장사항에서 언급했듯이 개발자는 많은 경우(특히 모바일에서) 종료됨 상태로의 전환을 안정적으로 감지할 수 없으므로 종료 이벤트(예: beforeunload, pagehide, unload)에 의존하는 개발자는 데이터가 손실될 수 있다는 사실을 아는 것이 매우 중요합니다.

Discarded

페이지가 삭제될 때 개발자는 discarded 상태를 관찰할 수 없습니다. 이는 일반적으로 페이지가 리소스 제약 조건에 따라 삭제되기 때문이며, 대부분의 경우 삭제 이벤트에 응답하여 스크립트가 실행되도록 페이지 고정을 해제할 수 없기 때문입니다.

따라서 숨김에서 고정으로 변경되는 변경사항이 삭제될 가능성에 대비해야 합니다. 그런 다음 페이지 로드 시 document.wasDiscarded를 선택하여 삭제된 페이지의 복원에 대응할 수 있습니다.

수명 주기 이벤트의 안정성과 순서가 모든 브라우저에서 일관되게 구현되지는 않으므로 표의 내용을 가장 쉽게 따르는 방법은 PageLifecycle.js를 사용하는 것입니다.

피해야 할 레거시 수명 주기 API

다음과 같은 이벤트는 가급적 피해야 합니다.

unload 이벤트

많은 개발자가 unload 이벤트를 보장된 콜백으로 취급하고 상태를 저장하고 분석 데이터를 전송하기 위한 세션 종료 신호로 사용하지만 이 작업은 특히 모바일에서 매우 불안정합니다. unload 이벤트는 모바일의 탭 전환기에서 탭을 닫거나 앱 전환기에서 브라우저 앱을 닫는 등 많은 일반적인 언로드 상황에서 실행되지 않습니다.

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

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

모든 최신 브라우저에서 항상 pagehide 이벤트를 사용하여 가능한 페이지 언로드 (종료됨 상태라고도 함)를 unload 이벤트로 감지하는 것이 좋습니다. 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 사용
  • 표 제목 또는 파비콘 업데이트
  • 알림 표시
  • 푸시 알림 전송

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

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

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

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

모든 인텐트와 상황에서 이러한 정지는 브라우저 정지가 CPU/배터리를 절약하기 위해 실행하는 것과 기능적으로 동일합니다. 따라서 정지 수명 주기 상태의 일부로 간주됩니다.

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

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

향후 IDBTransaction 객체에 commit() 메서드를 추가하여 개발자는 콜백이 필요하지 않은 효과적인 쓰기 전용 트랜잭션을 실행할 수 있습니다. 즉, 개발자가 IndexedDB에 데이터를 쓰기만 하고 읽기 및 쓰기로 구성된 복잡한 트랜잭션을 수행하지 않으면 (IndexedDB 데이터베이스가 이미 열려 있다고 가정) 태스크 큐가 정지되기 전에 commit() 메서드를 완료할 수 있습니다.

그러나 오늘날 작동해야 하는 코드의 경우 개발자는 다음 두 가지 옵션을 사용할 수 있습니다.

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

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

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

Chrome의 UI 삭제
Chrome 삭제 UI

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

요약

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

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