[๐Ÿค–] JavaScript requestIdleCallback๊ณผ isInputPending์œผ๋กœ UI ๋ฐ˜์‘์„ฑ ๊ฐœ์„ ํ•˜๊ธฐ: ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ตœ์ ํ™” ์‹ฌํ™”

JavaScript์˜ `requestIdleCallback`๊ณผ `isInputPending` API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น์„ ์ค„์ด๊ณ  UI ๋ฐ˜์‘์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ์‹ค์งˆ์ ์ธ ๋ฐฉ๋ฒ•์„ ๋ฐฐ์›Œ๋ณด์„ธ์š”. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ„ํ•œ ์›น ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต์„ ์•ˆ๋‚ดํ•ด ๋“œ๋ ค์š”.

19๋ถ„
๋‹จ์–ด: 1,697๊ฐœ
๊ฒŒ์‹œ๊ธ€ ์ธ๋„ค์ผ
์ •๋ณด

๐Ÿค– ์ด ํฌ์ŠคํŒ…์€ Gemini 2.5 Flash AI๊ฐ€ ์ž‘์„ฑํ–ˆ์–ด์š”.
๋‚ด์šฉ์˜ ์ •ํ™•์„ฑ์„ ์œ„ํ•ด ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณค์ง€๋งŒ, ์‹ค๋ฌด ์ ์šฉ ์ „ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ํ•จ๊ป˜ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”.

์œ ์šฉํ•œ ํŒ

JavaScript์˜ requestIdleCallback๊ณผ isInputPending API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น์„ ์ค„์ด๊ณ  UI ๋ฐ˜์‘์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ์‹ค์งˆ์ ์ธ ์›น ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต์„ ๋‹ค๋ค„์š”.

์•ˆ๋…•ํ•˜์„ธ์š”! 10๋…„ ์ด์ƒ ์‹ค๋ฌด ๊ฒฝํ—˜์„ ๊ฐ€์ง„ ์‹œ๋‹ˆ์–ด ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž์ด์ž ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ SEO ์ „๋ฌธ๊ฐ€, ๋ธ”๋ฃจ์ž…๋‹ˆ๋‹ค.
์ €๋Š” ์‹ค์ œ ์กด์žฌํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋Š” ์•„๋‹ˆ์ง€๋งŒ, ์—ฌ๋Ÿฌ๋ถ„์˜ ์›น ๊ฐœ๋ฐœ ์—ฌ์ •์— ์‹ค์งˆ์ ์ธ ๋„์›€์„ ๋“œ๋ฆฌ๊ณ ์ž ์ด ์ž๋ฆฌ์— ์™”์–ด์š”.

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค(UI)๊ฐ€ ๊ฐ‘์ž๊ธฐ ๋ฉˆ์ถ”๊ฑฐ๋‚˜, ์ž…๋ ฅ์— ๋Œ€ํ•œ ๋ฐ˜์‘์ด ๋А๋ ค์ง€๋Š” ํ˜„์ƒ์„ ๊ฒช์–ด๋ณด์…จ์„ ๊ฑฐ์˜ˆ์š”.
์ด๋Ÿฐ ํ˜„์ƒ์€ ์ฃผ๋กœ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฌด๊ฑฐ์šด ์ž‘์—…์œผ๋กœ ์ธํ•ด ๋ธ”๋กœํ‚น๋  ๋•Œ ๋ฐœ์ƒํ•˜๋Š”๋ฐ์š”. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์„ ํฌ๊ฒŒ ์ €ํ•ดํ•˜๋Š” ์š”์ธ์ด ๋˜์ฃ .
์˜ค๋Š˜์€ ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ  UI ๋ฐ˜์‘์„ฑ์„ ๊ทน๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ JavaScript API, requestIdleCallback๊ณผ isInputPending์— ๋Œ€ํ•ด ์‹ฌ๋„ ์žˆ๊ฒŒ ์•Œ์•„๋ณด๋ ค ํ•ด์š”.

๐Ÿšฆ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น, ์™œ ๋ฌธ์ œ์ผ๊นŒ์š”?

0๏ธโƒฃ ์›น ๋ธŒ๋ผ์šฐ์ €์˜ ํ•ต์‹ฌ, ๋ฉ”์ธ ์Šค๋ ˆ๋“œ

์›น ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹จ์ผ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๋Œ€๋ถ€๋ถ„์˜ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์š”.
DOM ์กฐ์ž‘, ์Šคํƒ€์ผ ๊ณ„์‚ฐ, ๋ ˆ์ด์•„์›ƒ, ํŽ˜์ธํŒ…, JavaScript ์‹คํ–‰, ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋“ฑ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ง€๋Š” ๋ชจ๋“  ์‹œ๊ฐ์  ์—…๋ฐ์ดํŠธ์™€ ์ƒํ˜ธ์ž‘์šฉ์ด ์ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ด๋ฃจ์–ด์ง€์ฃ .
๊ทธ๋ž˜์„œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฐ”๋น ์ง€๋ฉด ๋‹ค๋ฅธ ์ค‘์š”ํ•œ ์ž‘์—…๋“ค์ด ์ง€์—ฐ๋˜๊ฑฐ๋‚˜ ๋ฉˆ์ถ”๊ฒŒ ๋ผ์š”. ๋งˆ์น˜ ํ•˜๋‚˜์˜ ๊ธธ์— ๋„ˆ๋ฌด ๋งŽ์€ ์ฐจ๊ฐ€ ๋™์‹œ์— ์ง€๋‚˜๊ฐ€๋ ค ํ•  ๋•Œ ๊ตํ†ต ์ฒด์ฆ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ์ด์น˜์˜ˆ์š”.

1๏ธโƒฃ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ €ํ•˜๋กœ ์ด์–ด์ง€๋Š” ๋ฌธ์ œ๋“ค

๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ธ”๋กœํ‚น๋˜๋ฉด ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ• ๊นŒ์š”?

  • ๋А๋ฆฐ UI ๋ฐ˜์‘์„ฑ: ์‚ฌ์šฉ์ž์˜ ํด๋ฆญ์ด๋‚˜ ํ‚ค๋ณด๋“œ ์ž…๋ ฅ์— ๋Œ€ํ•œ ๋ฐ˜์‘์ด ๋Šฆ์–ด์ ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋Š”๋ฐ ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๊ฑฐ๋‚˜ ํ•œ์ฐธ ๋’ค์— ๋ฐ˜์‘ํ•˜์ฃ .
  • ๋ฒ„๋ฒ…์ด๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜: ์Šคํฌ๋กค๋ง์ด๋‚˜ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋ถ€๋“œ๋Ÿฝ์ง€ ์•Š๊ณ  ๋Š๊ธฐ๋Š” ํ˜„์ƒ(jank)์ด ๋ฐœ์ƒํ•ด์š”. ์ดˆ๋‹น 60ํ”„๋ ˆ์ž„(FPS)์„ ์œ ์ง€ํ•ด์•ผ ๋ถ€๋“œ๋Ÿฌ์šด ์›€์ง์ž„์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น์€ ์ด๋ฅผ ๋ฐฉํ•ดํ•ด์š”.
  • Core Web Vitals ์•…์˜ํ–ฅ: Google์˜ Core Web Vitals ์ง€ํ‘œ ์ค‘ FID(First Input Delay)๋‚˜ INP(Interaction to Next Paint)์— ๋ถ€์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์ณ์š”. ์ด๋Š” SEO์—๋„ ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์–ด์š”.

์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถ€์ •์ ์ธ ์ธ์ƒ์„ ์ฃผ๊ณ , ๊ฒฐ๊ตญ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดํƒˆ๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.
๊ทธ๋ž˜์„œ ๊ฐœ๋ฐœ์ž๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ ์ ˆํžˆ ๋ถ„์‚ฐ์‹œํ‚ค๋Š” ์ „๋žต์„ ๊ณ ๋ฏผํ•ด์•ผ ํ•ด์š”.

0๏ธโƒฃ requestIdleCallback์ด๋ž€?

requestIdleCallback์€ ๋ธŒ๋ผ์šฐ์ €์˜ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๊ฐ€ ์œ ํœด(idle) ์ƒํƒœ์ผ ๋•Œ, ์ฆ‰ ํ•  ์ผ์ด ์—†์„ ๋•Œ ํŠน์ • ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์Šค์ผ€์ค„๋งํ•˜๋Š” API์˜ˆ์š”.
์ด API๋Š” requestAnimationFrame๊ณผ ์œ ์‚ฌํ•˜์ง€๋งŒ, requestAnimationFrame์ด ๋‹ค์Œ ํ”„๋ ˆ์ž„ ๋ Œ๋”๋ง ์ง์ „์— ์‹คํ–‰๋˜์–ด ์‹œ๊ฐ์ ์ธ ์—…๋ฐ์ดํŠธ์— ์ตœ์ ํ™”๋˜์–ด ์žˆ๋‹ค๋ฉด, requestIdleCallback์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด๋‚˜ ์ค‘์š”ํ•œ ์ž‘์—…์„ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•œ ํ›„ ๋‚จ๋Š” ์‹œ๊ฐ„์— ์‹คํ–‰๋˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์–ด์š”.
์ฃผ๋กœ ์ค‘์š”๋„๊ฐ€ ๋‚ฎ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์ด๋‚˜ ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ „์†ก, DOM ๋ณ€๊ฒฝ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ๋“ฑ์— ํ™œ์šฉ๋œ๋‹ต๋‹ˆ๋‹ค.

์œ ์šฉํ•œ ํŒ

requestIdleCallback์€ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์–ด์š”. ํŠนํžˆ Safari์—์„œ๋Š” ๋ถ€๋ถ„์ ์œผ๋กœ ์ง€์›๋˜๊ฑฐ๋‚˜ ์ง€์›๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ์‚ฌ์šฉ ์‹œ ํด๋ฆฌํ•„(polyfill)์ด๋‚˜ ๊ธฐ๋Šฅ ๊ฐ์ง€(feature detection)๋ฅผ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์ข‹์•„์š”.

1๏ธโƒฃ requestIdleCallback ์‚ฌ์šฉ๋ฒ•

requestIdleCallback์€ ์ฝœ๋ฐฑ ํ•จ์ˆ˜์™€ ์˜ต์…˜ ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›์•„์š”.
์ฝœ๋ฐฑ ํ•จ์ˆ˜๋Š” IdleDeadline ๊ฐ์ฒด๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋ฐ, ์ด ๊ฐ์ฒด๋Š” timeRemaining() ๋ฉ”์„œ๋“œ์™€ didTimeout ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์š”.

  • timeRemaining(): ํ˜„์žฌ ํ”„๋ ˆ์ž„์—์„œ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋Š” ๋‚จ์€ ์‹œ๊ฐ„์„ ๋ฐ€๋ฆฌ์ดˆ ๋‹จ์œ„๋กœ ๋ฐ˜ํ™˜ํ•ด์š”.
  • didTimeout: ์ฝœ๋ฐฑ์ด ํƒ€์ž„์•„์›ƒ์œผ๋กœ ์ธํ•ด ์‹คํ–‰๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋ถˆ๋ฆฌ์–ธ ๊ฐ’์ด์—์š”.
interface IdleDeadline { readonly didTimeout: boolean; timeRemaining(): DOMHighResTimeStamp; } declare function requestIdleCallback( callback: (deadline: IdleDeadline) => void, options?: { timeout: number } ): number; declare function cancelIdleCallback(handle: number): void;

์˜ต์…˜ ๊ฐ์ฒด์—๋Š” timeout ์†์„ฑ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์–ด์š”.
timeout์€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ requestIdleCallback์„ ์ผ์ • ์‹œ๊ฐ„ ์•ˆ์— ๋ฐ˜๋“œ์‹œ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์•Œ๋ ค์ฃผ๋Š” ์—ญํ• ์„ ํ•ด์š”.
๋งŒ์•ฝ ์ง€์ •๋œ ์‹œ๊ฐ„ ์•ˆ์— ์œ ํœด ์‹œ๊ฐ„์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฉด, ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ฐ•์ œ๋กœ ์ฝœ๋ฐฑ์„ ์‹คํ–‰ํ•˜๊ณ  deadline.didTimeout์€ true๊ฐ€ ๋ผ์š”.

2๏ธโƒฃ ์‹ค์ „ ์˜ˆ์ œ: ๋ฌด๊ฑฐ์šด ์ž‘์—… ๋ถ„ํ•  ์ฒ˜๋ฆฌ

๊ฐ€์ƒ์˜ ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ ์ž‘์—…์„ requestIdleCallback์„ ์ด์šฉํ•ด ๋ถ„ํ•  ์ฒ˜๋ฆฌํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณผ๊นŒ์š”?
์ด ์˜ˆ์ œ์—์„œ๋Š” requestIdleCallback์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ํ˜ธ์ถœํ•˜์—ฌ ์ž‘์—…์„ ์ž‘์€ ๋‹จ์œ„๋กœ ์ชผ๊ฐœ์–ด ์ฒ˜๋ฆฌํ•˜๋Š” ํŒจํ„ด์„ ๋ณด์—ฌ๋“œ๋ ค์š”.

import React, { useState, useEffect, useRef } from 'react'; interface HeavyTaskProps { dataSize: number; } const HeavyTaskProcessor: React.FC<HeavyTaskProps> = ({ dataSize }) => { const [progress, setProgress] = useState(0); const [isProcessing, setIsProcessing] = useState(false); const taskHandleRef = useRef<number | null>(null); const dataRef = useRef<number[]>([]); useEffect(() => { // ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ dataRef.current = Array.from({ length: dataSize }, (_, i) => i); }, [dataSize]); const startProcessing = () => { setProgress(0); setIsProcessing(true); processChunk(0); }; const processChunk = (startIndex: number) => { if (startIndex >= dataRef.current.length) { setIsProcessing(false); console.log('Heavy task completed!'); return; } // requestIdleCallback์ด ์ง€์›๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ fallback if (!('requestIdleCallback' in window)) { console.warn('requestIdleCallback not supported, falling back to setTimeout.'); let endIndex = Math.min(startIndex + 100, dataRef.current.length); for (let i = startIndex; i < endIndex; i++) { // ๊ฐ€์ƒ ๋ฌด๊ฑฐ์šด ์ž‘์—…: ๊ฐ ๋ฐ์ดํ„ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ๋ณต์žกํ•œ ๊ณ„์‚ฐ ์ˆ˜ํ–‰ let result = 0; for (let j = 0; j < 10000; j++) { result += Math.sqrt(dataRef.current[i] * Math.random()); } } setProgress(Math.floor((endIndex / dataRef.current.length) * 100)); setTimeout(() => processChunk(endIndex), 0); return; } taskHandleRef.current = requestIdleCallback((deadline) => { let currentIndex = startIndex; // deadline.timeRemaining()์ด 0๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ didTimeout์ด true๊ฐ€ ๋  ๋•Œ๊นŒ์ง€ ์ž‘์—… while ( (deadline.timeRemaining() > 0 || deadline.didTimeout) && currentIndex < dataRef.current.length ) { // ๊ฐ€์ƒ ๋ฌด๊ฑฐ์šด ์ž‘์—…: ๊ฐ ๋ฐ์ดํ„ฐ ํ•ญ๋ชฉ์— ๋Œ€ํ•ด ๋ณต์žกํ•œ ๊ณ„์‚ฐ ์ˆ˜ํ–‰ let result = 0; for (let j = 0; j < 10000; j++) { result += Math.sqrt(dataRef.current[currentIndex] * Math.random()); } currentIndex++; } setProgress(Math.floor((currentIndex / dataRef.current.length) * 100)); // ๋‚จ์€ ์ž‘์—…์ด ์žˆ๋‹ค๋ฉด ๋‹ค์Œ ์œ ํœด ์‹œ๊ฐ„์— ๋‹ค์‹œ ์Šค์ผ€์ค„๋ง if (currentIndex < dataRef.current.length) { processChunk(currentIndex); } else { setIsProcessing(false); console.log('Heavy task completed!'); } }, { timeout: 100 }); // ์ตœ๋Œ€ 100ms ์•ˆ์—๋Š” ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅ }; const cancelProcessing = () => { if (taskHandleRef.current) { cancelIdleCallback(taskHandleRef.current); taskHandleRef.current = null; setIsProcessing(false); console.log('Heavy task cancelled!'); } }; return ( <div> <h2>requestIdleCallback ์˜ˆ์ œ</h2> <p>๋ฐ์ดํ„ฐ ํฌ๊ธฐ: {dataSize}</p> <p>์ง„ํ–‰๋ฅ : {progress}%</p> <button onClick={startProcessing} disabled={isProcessing}> {isProcessing ? '์ฒ˜๋ฆฌ ์ค‘...' : '๋ฌด๊ฑฐ์šด ์ž‘์—… ์‹œ์ž‘'} </button> <button onClick={cancelProcessing} disabled={!isProcessing} style={{ marginLeft: '10px' }}> ์ž‘์—… ์ทจ์†Œ </button> {isProcessing && <p>์ž‘์—…์ด ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ง„ํ–‰ ์ค‘์ด์—์š”. UI๋Š” ๋ฐ˜์‘์„ฑ์„ ์œ ์ง€ํ•  ๊ฑฐ์˜ˆ์š”.</p>} </div> ); }; export default HeavyTaskProcessor;
์ •๋ณด

์ด ์˜ˆ์ œ๋Š” requestIdleCallback์˜ ๋™์ž‘ ๋ฐฉ์‹์„ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ ๊ฒƒ์œผ๋กœ, ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ์›น ์›Œ์ปค(Web Workers)์™€ ๊ฐ™์€ ์ „์šฉ ์Šค๋ ˆ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ํšจ์œจ์ ์ธ ๊ฒฝ์šฐ๋„ ๋งŽ์•„์š”.
ํ•˜์ง€๋งŒ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ž‘์—…์ด๊ฑฐ๋‚˜ ์›น ์›Œ์ปค๋กœ ๋„˜๊ธฐ๊ธฐ ๋ณต์žกํ•œ ๊ฒฝ์šฐ requestIdleCallback์€ ์ข‹์€ ๋Œ€์•ˆ์ด ๋  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.

0๏ธโƒฃ isInputPending์ด๋ž€?

isInputPending์€ ํ˜„์žฌ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ(ํ‚ค๋ณด๋“œ, ๋งˆ์šฐ์Šค ๋“ฑ) ์ด๋ฒคํŠธ๊ฐ€ ๋Œ€๊ธฐ ์ค‘์ธ์ง€ ์—ฌ๋ถ€๋ฅผ ์•Œ๋ ค์ฃผ๋Š” API์˜ˆ์š”.
์ด API๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜๋Š” ๊ธด ์ž‘์—… ๋„์ค‘์— ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ๋“ค์–ด์™”๋Š”์ง€๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ฐ์ง€ํ•˜์—ฌ, ์ž‘์—…์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์กฐ์ ˆํ•˜๊ณ  UI ๋ฐ˜์‘์„ฑ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ผ์š”.
์˜ˆ๋ฅผ ๋“ค์–ด, ๋ณต์žกํ•œ ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กค์„ ํ•˜๊ฑฐ๋‚˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ๋‹ค๋ฉด, isInputPending์€ ์ด๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ํ˜„์žฌ ์ž‘์—…์„ ์ž ์‹œ ์ค‘๋‹จํ•œ ํ›„ ์ž…๋ ฅ ์ด๋ฒคํŠธ๋ฅผ ๋จผ์ € ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์–ด์š”.

๊ฒฝ๊ณ 

isInputPending์€ ์•„์ง ์‹คํ—˜์ ์ธ(experimental) API์ด๋ฉฐ, ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ด‘๋ฒ”์œ„ํ•˜๊ฒŒ ์ง€์›๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์–ด์š”. ํŠนํžˆ Firefox๋‚˜ Safari์—์„œ๋Š” ์ง€์›๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์œผ๋‹ˆ, ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์ฃผ์˜ ๊นŠ๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ด์š”.

1๏ธโƒฃ isInputPending ์‚ฌ์šฉ๋ฒ•

isInputPending์€ navigator.scheduling ๊ฐ์ฒด์— ์†ํ•ด ์žˆ์–ด์š”.
์ธ์ž ์—†์ด ํ˜ธ์ถœํ•˜๋ฉด ํ˜„์žฌ ๋ณด๋ฅ˜ ์ค‘์ธ ์ž…๋ ฅ์ด ์žˆ๋Š”์ง€ true/false๋ฅผ ๋ฐ˜ํ™˜ํ•ด์š”.
์˜ต์…˜ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด isInputPending์ด ๊ฐ์ง€ํ•  ์ž…๋ ฅ ์œ ํ˜•์„ ํ•„ํ„ฐ๋งํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.

interface Navigator { readonly scheduling: Scheduling; } interface Scheduling { isInputPending(options?: { includeContinuous: boolean }): boolean; }
  • includeContinuous: true๋กœ ์„ค์ •ํ•˜๋ฉด ์—ฐ์†์ ์ธ ์ž…๋ ฅ(์˜ˆ: ์Šคํฌ๋กค, ๋งˆ์šฐ์Šค ์ด๋™)๋„ ๊ฐ์ง€ํ•ด์š”. ๊ธฐ๋ณธ๊ฐ’์€ false๋กœ, ์ด ๊ฒฝ์šฐ ์ด์‚ฐ์ ์ธ ์ž…๋ ฅ(์˜ˆ: ํด๋ฆญ, ํ‚ค๋ณด๋“œ ์ž…๋ ฅ)๋งŒ ๊ฐ์ง€ํ•ด์š”.

2๏ธโƒฃ ์‹ค์ „ ์˜ˆ์ œ: ๋ฌด๊ฑฐ์šด ์ž‘์—… ์ค‘ ์ž…๋ ฅ ๊ฐ์ง€

requestIdleCallback ์˜ˆ์ œ์™€ ์œ ์‚ฌํ•˜๊ฒŒ, ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋„์ค‘์— isInputPending์„ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋ฐœ์ƒํ•˜๋ฉด ์ž‘์—…์„ ์ž ์‹œ ๋ฉˆ์ถ”๊ณ  ์ž…๋ ฅ์„ ์šฐ์„  ์ฒ˜๋ฆฌํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ๋ณด์—ฌ๋“œ๋ฆด๊ฒŒ์š”.
์ด ํŒจํ„ด์€ ํŠนํžˆ ๊ธด ๋ฃจํ”„๋‚˜ ๋ฐ˜๋ณต์ ์ธ ๊ณ„์‚ฐ ์ž‘์—…์—์„œ ์œ ์šฉํ•ด์š”.

import React, { useState, useRef } from 'react'; const InputAwareHeavyTask: React.FC = () => { const [isProcessing, setIsProcessing] = useState(false); const [progress, setProgress] = useState(0); const taskRef = useRef<{ isCancelled: boolean } | null>(null); const startHeavyComputation = () => { setIsProcessing(true); setProgress(0); taskRef.current = { isCancelled: false }; runComputation(0); }; const runComputation = (iteration: number) => { if (taskRef.current?.isCancelled) { setIsProcessing(false); console.log('Computation cancelled by user.'); return; } if (iteration >= 100) { // ์ด 100๋ฒˆ์˜ ํฐ ์ž‘์—… ๋ฐ˜๋ณต setIsProcessing(false); console.log('Heavy computation finished!'); return; } // ๋งค ๋ฐ˜๋ณต๋งˆ๋‹ค isInputPending์„ ์ฒดํฌํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ์žˆ๋Š”์ง€ ํ™•์ธ // navigator.scheduling์ด ์—†๊ฑฐ๋‚˜ isInputPending์ด ์—†์œผ๋ฉด ์ฒดํฌํ•˜์ง€ ์•Š์Œ if ( navigator.scheduling && navigator.scheduling.isInputPending && navigator.scheduling.isInputPending({ includeContinuous: true }) ) { console.log('User input pending! Pausing computation to handle input.'); // ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๊ฐ์ง€๋˜๋ฉด ํ˜„์žฌ ์ž‘์—…์„ ์ž ์‹œ ์ค‘๋‹จํ•˜๊ณ  ๋‹ค์Œ ์ด๋ฒคํŠธ ๋ฃจํ”„ ํ‹ฑ์—์„œ ๋‹ค์‹œ ์‹œ์ž‘ setTimeout(() => runComputation(iteration), 0); return; } // ์‹ค์ œ ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ (์˜ˆ์‹œ๋กœ ๋‹จ์ˆœํ™”) let sum = 0; for (let i = 0; i < 10000000; i++) { sum += Math.random(); } console.log(`Iteration ${iteration} completed, sum: ${sum}`); setProgress(Math.floor(((iteration + 1) / 100) * 100)); // ๋‹ค์Œ ๋ฐ˜๋ณต์€ ๋‹ค์Œ ์ด๋ฒคํŠธ ๋ฃจํ”„ ํ‹ฑ์—์„œ ์‹คํ–‰ํ•˜์—ฌ UI ์—…๋ฐ์ดํŠธ ๊ธฐํšŒ ์ œ๊ณต setTimeout(() => runComputation(iteration + 1), 0); }; const cancelComputation = () => { if (taskRef.current) { taskRef.current.isCancelled = true; } }; return ( <div> <h2>isInputPending ์˜ˆ์ œ</h2> <p>์ง„ํ–‰๋ฅ : {progress}%</p> <button onClick={startHeavyComputation} disabled={isProcessing}> {isProcessing ? '๊ณ„์‚ฐ ์ค‘...' : '๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ ์‹œ์ž‘'} </button> <button onClick={cancelComputation} disabled={!isProcessing} style={{ marginLeft: '10px' }}> ๊ณ„์‚ฐ ์ทจ์†Œ </button> {isProcessing && <p>๊ณ„์‚ฐ์ด ์ง„ํ–‰ ์ค‘์ด์ง€๋งŒ, ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์‹œ ์ฆ‰์‹œ ๋ฐ˜์‘ํ•  ๊ฑฐ์˜ˆ์š”.</p>} <input type="text" placeholder="์—ฌ๊ธฐ์— ์ž…๋ ฅํ•ด๋ณด์„ธ์š”" style={{ marginTop: '20px', padding: '10px', display: 'block' }} /> </div> ); }; export default InputAwareHeavyTask;

์ด ์˜ˆ์ œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋ฌด๊ฑฐ์šด ๊ณ„์‚ฐ์ด ์ง„ํ–‰๋˜๋Š” ๋™์•ˆ <input> ํ•„๋“œ์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•ด๋ณด์‹œ๋ฉด, ๊ณ„์‚ฐ์ด ์ž ์‹œ ๋ฉˆ์ถ”๊ณ  ์ž…๋ ฅ์ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”.
isInputPending ๋•๋ถ„์— ์‚ฌ์šฉ์ž์˜ ์ƒํ˜ธ์ž‘์šฉ์ด ์ง€์—ฐ๋˜์ง€ ์•Š๊ณ  ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜๋Š” ๊ฒƒ์ด์ฃ .

์ด ๋‘ API๋Š” ๊ฐ๊ฐ ๋‹ค๋ฅธ ๋ชฉ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ, ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ตœ์ ํ™”์— ๊ฐ•๋ ฅํ•œ ์‹œ๋„ˆ์ง€๋ฅผ ๋‚ผ ์ˆ˜ ์žˆ์–ด์š”.

  • requestIdleCallback: ์ค‘์š”๋„๊ฐ€ ๋‚ฎ์€ ์ž‘์—…์„ ๋ธŒ๋ผ์šฐ์ €์˜ ์œ ํœด ์‹œ๊ฐ„์— ์Šค์ผ€์ค„๋งํ•˜์—ฌ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น์„ ํšŒํ”ผํ•ด์š”.
  • isInputPending: ์ง„ํ–‰ ์ค‘์ธ ๋ฌด๊ฑฐ์šด ์ž‘์—… ๋„์ค‘์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๊ฐ์ง€ํ•˜์—ฌ, ์ž…๋ ฅ์— ๋Œ€ํ•œ ์ฆ‰๊ฐ์ ์ธ ๋ฐ˜์‘์„ ๋ณด์žฅํ•ด์š”.

์ฆ‰, requestIdleCallback์œผ๋กœ ์ž‘์—…์„ ๋ถ„์‚ฐ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ, ๋ถ„์‚ฐ๋œ ๊ฐ ์ž‘์—… ์ฒญํฌ ๋‚ด๋ถ€์—์„œ isInputPending์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ฒดํฌํ•˜์—ฌ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋Œ€ํ•œ ์šฐ์„ ์ˆœ์œ„๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „๋ฐ˜์ ์ธ UI ๋ฐ˜์‘์„ฑ๊ณผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด์š”.

๐Ÿ“ ์ •๋ฆฌํ•˜๋ฉฐ

์˜ค๋Š˜์€ ์›น ์„ฑ๋Šฅ ์ตœ์ ํ™”์˜ ํ•ต์‹ฌ ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ธ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด requestIdleCallback๊ณผ isInputPending ๋‘ ๊ฐ€์ง€ ๊ฐ•๋ ฅํ•œ JavaScript API๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด์•˜์–ด์š”.

  • requestIdleCallback์€ ๋ธŒ๋ผ์šฐ์ €์˜ ์œ ํœด ์‹œ๊ฐ„์„ ํ™œ์šฉํ•˜์—ฌ ์ค‘์š”๋„๊ฐ€ ๋‚ฎ์€ ์ž‘์—…์„ ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์˜ ๋ถ€๋‹ด์„ ์ค„์—ฌ์ค˜์š”.
  • isInputPending์€ ์ง„ํ–‰ ์ค‘์ธ ์ž‘์—… ๋„์ค‘์— ์‚ฌ์šฉ์ž ์ž…๋ ฅ์ด ๋ฐœ์ƒํ–ˆ๋Š”์ง€ ๊ฐ์ง€ํ•˜์—ฌ, UI ๋ฐ˜์‘์„ฑ์„ ์ตœ์šฐ์„ ์œผ๋กœ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค˜์š”.

์ด ๋‘ API๋ฅผ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜๋ฉด, ๋ฌด๊ฑฐ์šด ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋„ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ถ€๋“œ๋Ÿฝ๊ณ  ๋ฐ˜์‘์„ฑ ์ข‹์€ ๊ฒฝํ—˜์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”.
๋ฌผ๋ก , ์ด ์™ธ์—๋„ ์›น ์›Œ์ปค(Web Workers)๋‚˜ setTimeout(..., 0)์„ ์ด์šฉํ•œ ์ž‘์—… ๋ถ„ํ• , useTransition/useDeferredValue (React์˜ ๊ฒฝ์šฐ) ๋“ฑ ๋‹ค์–‘ํ•œ ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋“ค์ด ์žˆ์œผ๋‹ˆ, ์—ฌ๋Ÿฌ๋ถ„์˜ ํ”„๋กœ์ ํŠธ ์ƒํ™ฉ์— ๋งž์ถฐ ๊ฐ€์žฅ ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜์‹œ๊ธธ ๊ถŒํ•ด๋“œ๋ ค์š”.

๋ณต์žกํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ๋•Œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜๋ž๋‹ˆ๋‹ค.
์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ๋” ๋น ๋ฅด๊ณ  ๋ฐ˜์‘์„ฑ ์ข‹์€ ์›น์„ ๋งŒ๋“ค์–ด๋‚˜๊ฐ€์‹œ๊ธธ ๋ฐ”๋ผ์š”!

๐Ÿ“ฎ ์ฐธ๊ณ 

์—ฐ๊ด€๋œ ํฌ์ŠคํŠธ