1. 소개
다음 페인트에 대한 상호작용 (INP)에 관해 알아볼 수 있는 대화형 데모 및 Codelab입니다.
기본 요건
- HTML 및 JavaScript 개발에 관한 지식
- 권장사항: INP 문서를 읽어보세요.
학습 내용
- 사용자 상호작용과 이러한 상호작용의 처리가 페이지 응답성에 미치는 영향
- 원활한 사용자 환경을 위해 지연을 줄이고 없애는 방법
필요한 항목
- GitHub에서 코드를 클론하고 npm 명령어를 실행할 수 있는 컴퓨터
- 텍스트 편집기
- 모든 상호작용 측정값이 작동하려면 최신 버전의 Chrome이 필요합니다.
2. 설정
코드 가져오기 및 실행
코드는 web-vitals-codelabs
저장소에 있습니다.
- 터미널에서 저장소를 클론합니다.
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
- 클론된 디렉터리(
cd web-vitals-codelabs/understanding-inp
)로 이동합니다. - 종속 항목 설치:
npm ci
- 웹 서버를 시작합니다.
npm run start
- 브라우저에서 http://localhost:5173/understanding-inp/을 방문합니다.
앱 개요
페이지 상단에는 점수 카운터와 증가 버튼이 있습니다. 반응성과 응답성의 클래식 데모입니다.
버튼 아래에는 다음 네 가지 측정값이 표시됩니다.
- INP: 현재 INP 점수입니다. 일반적으로 가장 심각한 상호작용입니다.
- 상호작용: 가장 최근 상호작용의 점수입니다.
- FPS: 페이지의 기본 스레드 초당 프레임 수입니다.
- 타이머: 버벅거림을 시각화하는 데 도움이 되는 실행 중인 타이머 애니메이션입니다.
FPS 및 타이머 항목은 상호작용을 측정하는 데 전혀 필요하지 않습니다. 반응형을 시각화하기 쉽도록 추가되었습니다.
사용해 보기
증가 버튼과 상호작용하여 점수가 증가하는 것을 확인합니다. INP 및 상호작용 값은 각 증분마다 변경되나요?
INP는 사용자가 상호작용한 시점부터 페이지가 렌더링된 업데이트를 사용자에게 실제로 표시하는 데 걸리는 시간을 측정합니다.
3. Chrome DevTools로 상호작용 측정
도구 더보기 > 개발자 도구 메뉴에서 DevTools를 열거나, 페이지를 마우스 오른쪽 버튼으로 클릭하고 검사를 선택하거나 단축키를 사용합니다.
상호작용을 측정하는 데 사용할 성능 패널로 전환합니다.
다음으로 성능 패널에서 상호작용을 캡처합니다.
- 녹화를 누릅니다.
- 페이지와 상호작용합니다 (증가 버튼 누르기).
- 녹화를 중지합니다.
결과 타임라인에 상호작용 트랙이 표시됩니다. 왼쪽의 삼각형을 클릭하여 펼칩니다.
두 개의 상호작용이 표시됩니다. 스크롤하거나 W 키를 누른 상태로 두 번째 이미지를 확대합니다.
상호작용 위로 마우스를 가져가면 상호작용이 빠르고 처리 시간이 없으며 입력 지연과 표시 지연이 최소한의 시간임을 알 수 있습니다. 정확한 길이는 컴퓨터 속도에 따라 달라집니다.
4. 장기 실행 이벤트 리스너
index.js
파일을 열고 이벤트 리스너 내에서 blockFor
함수를 주석 해제합니다.
전체 코드 보기: click_block.html
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
});
파일을 저장합니다. 서버에서 변경사항을 확인하고 페이지를 새로고침합니다.
페이지와 다시 상호작용해 보세요. 이제 상호작용이 눈에 띄게 느려집니다.
성능 추적
성능 패널에서 다른 녹화 파일을 가져와서 어떻게 표시되는지 확인하세요.
이전에는 짧은 상호작용이었지만 이제는 1초가 걸립니다.
상호작용 위로 마우스를 가져가면 시간이 거의 전적으로 이벤트 리스너 콜백을 실행하는 데 걸리는 시간인 '처리 기간'에 소요되는 것을 확인할 수 있습니다. 차단 blockFor
호출이 이벤트 리스너 내에 완전히 있으므로 시간이 여기에 소요됩니다.
5. 실험: 처리 기간
이벤트 리스너 작업을 재정렬하여 INP에 미치는 영향을 확인해 보세요.
UI를 먼저 업데이트
JS 호출 순서를 바꿔서 UI를 먼저 업데이트한 다음 차단하면 어떻게 되나요?
전체 코드 보기: ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
UI가 더 일찍 표시되는 것을 확인하셨나요? 순서가 INP 점수에 영향을 미치나요?
트레이스를 가져와 상호작용을 검사하여 차이가 있는지 확인해 보세요.
별도의 리스너
작업을 별도의 이벤트 리스너로 이동하면 어떻게 될까요? 하나의 이벤트 리스너에서 UI를 업데이트하고 별도의 리스너에서 페이지를 차단합니다.
전체 코드 보기: two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
이제 성능 패널에서 어떻게 표시되나요?
다양한 이벤트 유형
대부분의 상호작용은 포인터 또는 키 이벤트에서부터 호버, 포커스/블러, beforechange 및 beforeinput과 같은 합성 이벤트에 이르기까지 다양한 유형의 이벤트를 발생시킵니다.
실제 페이지에는 다양한 이벤트의 리스너가 있습니다.
이벤트 리스너의 이벤트 유형을 변경하면 어떻게 되나요? 예를 들어 click
이벤트 리스너 중 하나를 pointerup
또는 mouseup
로 대체할 수 있나요?
전체 코드 보기: diff_handlers.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
UI 업데이트 없음
이벤트 리스너에서 UI 업데이트 호출을 삭제하면 어떻게 되나요?
전체 코드 보기: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
6. 처리 기간 실험 결과
성능 추적: UI를 먼저 업데이트
전체 코드 보기: ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
버튼을 클릭하는 성능 패널 기록을 보면 결과가 변경되지 않았습니다. 차단 코드 전에 UI 업데이트가 트리거되었지만 브라우저는 이벤트 리스너가 완료될 때까지 화면에 그려진 내용을 실제로 업데이트하지 않았습니다. 즉, 상호작용이 완료되는 데 여전히 1초가 넘게 걸렸습니다.
성능 추적: 리스너 분리
전체 코드 보기: two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
기능상 차이는 없습니다. 상호작용은 여전히 1초가 걸립니다.
클릭 상호작용을 확대하면 click
이벤트의 결과로 실제로 두 가지 다른 함수가 호출되는 것을 확인할 수 있습니다.
예상대로 첫 번째(UI 업데이트)는 매우 빠르게 실행되는 반면 두 번째는 1초가 걸립니다. 하지만 이러한 효과를 합하면 최종 사용자에게 동일한 느린 상호작용이 발생합니다.
성능 추적: 다양한 이벤트 유형
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
결과가 매우 유사합니다. 상호작용은 여전히 1초입니다. 유일한 차이점은 더 짧은 UI 업데이트 전용 click
리스너가 차단 pointerup
리스너 후에 실행된다는 것입니다.
성능 트레이스: UI 업데이트 없음
전체 코드 보기: no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
- 점수는 업데이트되지 않지만 페이지는 업데이트됩니다.
- 애니메이션, CSS 효과, 기본 웹 구성요소 작업 (양식 입력), 텍스트 입력, 텍스트 강조 표시가 모두 계속 업데이트됩니다.
이 경우 버튼을 클릭하면 활성 상태로 전환되었다가 다시 돌아가므로 브라우저에서 페인트해야 합니다. 즉, INP가 여전히 존재합니다.
이벤트 리스너가 페이지가 그려지지 않도록 1초 동안 기본 스레드를 차단했으므로 상호작용은 여전히 1초가 걸립니다.
성능 패널 녹화는 이전과 거의 동일한 상호작용을 보여줍니다.
핵심
모든 이벤트 리스너에서 실행되는 코드는 상호작용을 지연시킵니다.
- 여기에는 다양한 스크립트에서 등록된 리스너와 리스너에서 실행되는 프레임워크 또는 라이브러리 코드(예: 구성요소 렌더링을 트리거하는 상태 업데이트)가 포함됩니다.
- 자체 코드뿐만 아니라 모든 서드 파티 스크립트
일반적인 문제입니다.
마지막으로, 코드에서 페인트를 트리거하지 않는다고 해서 느린 이벤트 리스너가 완료될 때까지 페인트가 대기하지 않는다는 의미는 아닙니다.
7. 실험: 입력 지연
이벤트 리스너 외부에서 실행되는 긴 코드는 어떻게 되나요? 예를 들면 다음과 같습니다.
- 로드 중에 페이지를 무작위로 차단하는
<script>
가 늦게 로드되는 경우 - 페이지를 주기적으로 차단하는
setInterval
와 같은 API 호출이 있나요?
이벤트 리스너에서 blockFor
를 삭제하고 setInterval()
에 추가해 보세요.
전체 코드 보기: input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
어떤 일이 일어나나요?
8. 입력 지연 실험 결과
전체 코드 보기: input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
setInterval
차단 작업이 실행되는 동안 발생하는 버튼 클릭을 기록하면 상호작용 자체에서 차단 작업이 실행되지 않더라도 상호작용이 오래 실행됩니다.
이러한 장기 실행 기간을 긴 작업이라고 하는 경우가 많습니다.
DevTools에서 상호작용 위로 마우스를 가져가면 상호작용 시간이 이제 처리 기간이 아닌 입력 지연에 주로 기여하는 것을 확인할 수 있습니다.
상호작용에 항상 영향을 미치는 것은 아닙니다. 작업이 실행될 때 클릭하지 않으면 운이 좋을 수도 있습니다. 이러한 '무작위' 재채기는 때때로만 문제를 일으키는 경우 디버깅하기가 매우 어렵습니다.
이러한 문제를 추적하는 한 가지 방법은 긴 작업 (또는 긴 애니메이션 프레임)과 총 차단 시간을 측정하는 것입니다.
9. 프레젠테이션 속도가 느림
지금까지 입력 지연 또는 이벤트 리스너를 통해 JavaScript의 성능을 살펴봤습니다. 하지만 다음 페인트 렌더링에 영향을 미치는 다른 요소는 무엇일까요?
비싼 효과로 페이지를 업데이트하는 것과 같습니다.
페이지 업데이트가 빠르게 이루어지더라도 브라우저가 이를 렌더링하기 위해 열심히 작업해야 할 수 있습니다.
기본 스레드에서 다음을 실행합니다.
- 상태 변경 후 업데이트를 렌더링해야 하는 UI 프레임워크
- DOM 변경 또는 비용이 많이 드는 CSS 쿼리 선택기를 많이 전환하면 스타일, 레이아웃, 페인트가 많이 트리거될 수 있습니다.
기본 스레드에서:
- CSS를 사용하여 GPU 효과 지원
- 매우 큰 고해상도 이미지 추가
- SVG/캔버스를 사용하여 복잡한 장면 그리기
웹에서 흔히 볼 수 있는 예는 다음과 같습니다.
- 초기 시각적 피드백을 제공하기 위해 일시중지하지 않고 링크를 클릭한 후 전체 DOM을 다시 빌드하는 SPA 사이트
- 동적 사용자 인터페이스를 사용하여 복잡한 검색 필터를 제공하지만 이를 위해 비용이 많이 드는 리스너를 실행하는 검색 페이지
- 전체 페이지의 스타일/레이아웃을 트리거하는 어두운 모드 전환
10. 실험: 표시 지연
느린 requestAnimationFrame
requestAnimationFrame()
API를 사용하여 긴 프레젠테이션 지연을 시뮬레이션해 보겠습니다.
이벤트 리스너가 반환된 후에 실행되도록 blockFor
호출을 requestAnimationFrame
콜백으로 이동합니다.
전체 코드 보기: presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
어떤 일이 일어나나요?
11. 표시 지연 실험 결과
전체 코드 보기: presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
상호작용은 1초 동안 유지되는데 무슨 일이 일어난 걸까요?
requestAnimationFrame
은 다음 페인트 전에 콜백을 요청합니다. INP는 상호작용부터 다음 페인트까지의 시간을 측정하므로 requestAnimationFrame
의 blockFor(1000)
가 다음 페인트를 1초 동안 계속 차단합니다.
하지만 다음 두 가지 사항에 유의하세요.
- 마우스를 가져가면 이제 이벤트 리스너가 반환된 후 기본 스레드 차단이 발생하므로 모든 상호작용 시간이 '프레젠테이션 지연'에 사용되는 것을 확인할 수 있습니다.
- 기본 스레드 활동의 루트가 더 이상 클릭 이벤트가 아니라 '애니메이션 프레임 발생'입니다.
12. 상호작용 진단
이 테스트 페이지에서는 점수, 타이머, 카운터 UI 등 반응성이 매우 시각적이지만 평균 페이지를 테스트할 때는 더 미묘합니다.
상호작용이 오래 걸리는 경우 원인이 무엇인지 명확하지 않은 경우가 많습니다.
- 입력 지연이 있나요?
- 이벤트 처리 기간
- 표시 지연이 있나요?
원하는 페이지에서 DevTools를 사용하여 반응성을 측정할 수 있습니다. 이 습관을 들이려면 다음 흐름을 시도해 보세요.
- 평소와 같이 웹을 탐색합니다.
- DevTools 성능 패널의 실시간 측정항목 보기에서 상호작용 로그를 주시하세요.
- 성능이 좋지 않은 상호작용이 표시되면 다음과 같이 반복해 보세요.
- 반복할 수 없는 경우 상호작용 로그를 사용하여 통계를 확인하세요.
- 문제를 반복할 수 있다면 '성능' 패널에서 트레이스를 기록하세요.
모든 지연
페이지에 이러한 문제를 조금씩 추가해 보세요.
전체 코드: all_the_things.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
그런 다음 콘솔과 성능 패널을 사용하여 문제를 진단하세요.
13. 실험: 비동기 작업
상호작용 내에서 시각적이지 않은 효과(예: 네트워크 요청, 타이머 시작, 전역 상태 업데이트)를 시작할 수 있으므로 이러한 효과가 결국 페이지를 업데이트하면 어떻게 될까요?
브라우저에서 실제로 새로운 렌더링 업데이트가 필요하지 않다고 판단하더라도 상호작용 후 다음 페인트가 렌더링될 수 있는 한 상호작용 측정이 중지됩니다.
이를 시도하려면 클릭 리스너에서 UI를 계속 업데이트하되 제한 시간에서 차단 작업을 실행하세요.
전체 코드 보기: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
이제 어떻게 되나요?
14. 비동기 작업 실험 결과
전체 코드 보기: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
이제 UI가 업데이트된 후 기본 스레드를 즉시 사용할 수 있으므로 상호작용이 짧습니다. 긴 차단 작업은 계속 실행되지만 페인트 후 실행되므로 사용자에게 즉각적인 UI 피드백이 제공됩니다.
교훈: 삭제할 수 없다면 최소한 이동하세요.
메서드
고정된 100밀리초 setTimeout
보다 더 나은 방법을 사용할 수 있을까요? 코드가 최대한 빨리 실행되기를 원할 것입니다. 그렇지 않다면 코드를 삭제했을 것입니다.
목표:
- 상호작용에서
incrementAndUpdateUI()
이 실행됩니다. blockFor()
는 최대한 빨리 실행되지만 다음 페인트를 차단하지는 않습니다.- 이렇게 하면 '마법의 시간 제한' 없이 예측 가능한 동작이 발생합니다.
이를 달성하는 방법에는 다음이 포함됩니다.
setTimeout(0)
Promise.then()
requestAnimationFrame
requestIdleCallback
scheduler.postTask()
'requestPostAnimationFrame'
단독으로 사용되는 requestAnimationFrame
(다음 페인트 전에 실행하려고 시도하며 일반적으로 여전히 상호작용이 느림)와 달리 requestAnimationFrame
+ setTimeout
는 requestPostAnimationFrame
의 간단한 폴리필을 만들어 다음 페인트 후에 콜백을 실행합니다.
전체 코드: raf+task.html
function afterNextPaint(callback) {
requestAnimationFrame(() => {
setTimeout(callback, 0);
});
}
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
afterNextPaint(() => {
blockFor(1000);
});
});
인체공학적 측면에서 프라미스로 래핑할 수도 있습니다.
전체 코드: raf+task2.html
async function nextPaint() {
return new Promise(resolve => afterNextPaint(resolve));
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await nextPaint();
blockFor(1000);
});
15. 여러 상호작용 (및 격분 클릭)
긴 차단 작업을 이동하면 도움이 될 수 있지만 이러한 긴 작업은 여전히 페이지를 차단하여 향후 상호작용은 물론 다른 많은 페이지 애니메이션과 업데이트에도 영향을 미칩니다.
페이지의 비동기 차단 작업 버전을 다시 시도해 보세요 (또는 마지막 단계에서 작업 지연에 관한 자체 변형을 생각해 낸 경우 자체 버전을 시도해 보세요).
전체 코드 보기: timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
여러 번 빠르게 클릭하면 어떻게 되나요?
성능 추적
각 클릭마다 1초 길이의 작업이 대기열에 추가되어 기본 스레드가 상당한 시간 동안 차단됩니다.
이러한 긴 작업이 새로 들어오는 클릭과 겹치면 이벤트 리스너 자체가 거의 즉시 반환되더라도 상호작용이 느려집니다. 입력 지연이 있는 이전 실험과 동일한 상황을 만들었습니다. 이번에는 입력 지연이 setInterval
에서 발생하는 것이 아니라 이전 이벤트 리스너에 의해 트리거된 작업에서 발생합니다.
전략
이상적으로는 긴 작업을 완전히 삭제하는 것이 좋습니다.
- 불필요한 코드(특히 스크립트)를 완전히 삭제합니다.
- 긴 작업을 실행하지 않도록 코드를 최적화합니다.
- 새로운 상호작용이 도착하면 오래된 작업을 중단합니다.
16. 전략 1: 디바운스
클래식 전략입니다. 상호작용이 연속으로 도착하고 처리 또는 네트워크 효과에 비용이 많이 드는 경우 취소하고 다시 시작할 수 있도록 의도적으로 작업 시작을 지연하세요. 이 패턴은 자동 완성 필드와 같은 사용자 인터페이스에 유용합니다.
setTimeout
를 사용하여 타이머(예: 500~1,000밀리초)로 비용이 많이 드는 작업의 시작을 지연합니다.- 이때 타이머 ID를 저장합니다.
- 새로운 상호작용이 도착하면
clearTimeout
를 사용하여 이전 타이머를 취소합니다.
전체 코드 보기: debounce.html
let timer;
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
blockFor(1000);
}, 1000);
});
성능 추적
여러 번 클릭해도 하나의 blockFor
작업만 실행되며, 실행되기 전에 1초 동안 클릭이 없을 때까지 기다립니다. 텍스트 입력에 입력하거나 여러 번의 빠른 클릭이 예상되는 항목 타겟과 같이 버스트로 발생하는 상호작용의 경우 기본적으로 사용하기에 이상적인 전략입니다.
17. 전략 2: 장기 실행 작업 중단
디바운스 기간이 지난 직후에 추가 클릭이 발생하여 긴 작업 중간에 도달하고 입력 지연으로 인해 매우 느린 상호작용이 될 수도 있습니다.
작업 중에 상호작용이 발생하면 새 상호작용이 즉시 처리되도록 바쁜 작업을 일시중지하는 것이 좋습니다. 어떻게 하면 되나요?
isInputPending
와 같은 API가 있지만 일반적으로 긴 작업을 청크로 분할하는 것이 좋습니다.
setTimeout
이 많음
첫 번째 시도: 간단한 작업을 수행합니다.
전체 코드 보기: small_tasks.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
setTimeout(() => blockFor(100), 0);
});
});
이렇게 하면 브라우저에서 각 작업을 개별적으로 예약할 수 있으며 입력이 더 높은 우선순위를 가질 수 있습니다.
5번의 클릭으로 5초의 작업을 수행할 수 있지만 클릭당 1초 작업이 100밀리초 작업 10개로 나뉘었습니다. 따라서 이러한 작업과 겹치는 여러 상호작용이 있더라도 100밀리초를 초과하는 입력 지연이 있는 상호작용은 없습니다. 브라우저는 setTimeout
작업보다 수신 이벤트 리스너를 우선시하므로 상호작용이 응답 상태로 유지됩니다.
이 전략은 애플리케이션 로드 시간에 호출해야 하는 독립적인 기능이 많은 경우와 같이 별도의 진입점을 예약할 때 특히 효과적입니다. 스크립트를 로드하고 스크립트 평가 시간에 모든 것을 실행하면 기본적으로 거대한 긴 작업에서 모든 것이 실행될 수 있습니다.
하지만 이 전략은 공유 상태를 사용하는 for
루프와 같이 긴밀하게 결합된 코드를 분리하는 데는 적합하지 않습니다.
이제 yield()
하지만 최신 async
및 await
를 활용하여 JavaScript 함수에 'yield points'를 쉽게 추가할 수 있습니다.
예를 들면 다음과 같습니다.
전체 코드 보기: yieldy.html
// Polyfill for scheduler.yield()
async function schedulerDotYield() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function blockInPiecesYieldy(ms) {
const ms_per_part = 10;
const parts = ms / ms_per_part;
for (let i = 0; i < parts; i++) {
await schedulerDotYield();
blockFor(ms_per_part);
}
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await blockInPiecesYieldy(1000);
});
이전과 마찬가지로 작업 청크 후 기본 스레드가 양보되고 브라우저가 수신되는 상호작용에 응답할 수 있지만 이제 별도의 setTimeout
대신 await schedulerDotYield()
만 필요하므로 for
루프 중간에서도 사용할 수 있을 만큼 인체공학적입니다.
이제 AbortContoller()
이 방법은 작동했지만 새 상호작용이 들어와서 해야 할 작업이 변경되었을 수 있음에도 불구하고 각 상호작용은 더 많은 작업을 예약합니다.
디바운싱 전략을 사용하면 새 상호작용이 있을 때마다 이전 타임아웃이 취소되었습니다. 여기에서도 비슷한 작업을 할 수 있나요? 이를 위한 한 가지 방법은 AbortController()
를 사용하는 것입니다.
전체 코드 보기: aborty.html
// Polyfill for scheduler.yield()
async function schedulerDotYield() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
async function blockInPiecesYieldyAborty(ms, signal) {
const parts = ms / 10;
for (let i = 0; i < parts; i++) {
// If AbortController has been asked to stop, abandon the current loop.
if (signal.aborted) return;
await schedulerDotYield();
blockFor(10);
}
}
let abortController = new AbortController();
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
abortController.abort();
abortController = new AbortController();
await blockInPiecesYieldyAborty(1000, abortController.signal);
});
클릭이 들어오면 blockInPiecesYieldyAborty
for
루프가 시작되어 필요한 작업을 실행하는 동시에 브라우저가 새로운 상호작용에 계속 응답할 수 있도록 기본 스레드를 주기적으로 양보합니다.
두 번째 클릭이 들어오면 첫 번째 루프가 AbortController
로 취소된 것으로 표시되고 새 blockInPiecesYieldyAborty
루프가 시작됩니다. 첫 번째 루프가 다시 실행되도록 예약된 다음 번에는 signal.aborted
가 이제 true
임을 확인하고 추가 작업을 수행하지 않고 즉시 반환됩니다.
18. 결론
모든 긴 작업을 분할하면 사이트가 새로운 상호작용에 응답할 수 있습니다. 이를 통해 초기 의견을 빠르게 제공할 수 있으며 진행 중인 작업을 중단하는 등의 결정을 내릴 수도 있습니다. 이 경우 진입점을 별도의 작업으로 예약해야 할 수도 있습니다. 편리한 경우 'yield' 포인트를 추가하는 것이 좋습니다.
참고사항
- INP는 모든 상호작용을 측정합니다.
- 각 상호작용은 입력부터 다음 페인트까지 측정됩니다. 이는 사용자가 반응성을 보는 방식입니다.
- 입력 지연, 이벤트 처리 기간, 프레젠테이션 지연은 모두 상호작용 반응성에 영향을 미칩니다.
- DevTools를 사용하면 INP와 상호작용 분석을 쉽게 측정할 수 있습니다.
전략
- 페이지에 장기 실행 코드 (긴 작업)가 없어야 합니다.
- 다음 페인트까지 불필요한 코드를 이벤트 리스너에서 이동합니다.
- 렌더링 업데이트 자체가 브라우저에 효율적인지 확인합니다.