
์์งํ ๋งํ๋ฉด ์ ๋ ์์ ์ ์ฝ๋ฉ์ ์ฒ์ ๋ฐฐ์ธ ๋ <script> ํ๊ทธ๋ฅผ <body> ๋งจ ์๋์ ๋๋ ๊ฒ ๊ทธ๋ฅ "๊ด๋ก"์ธ ์ค๋ง ์์์ด์.
์ค์ ๋ก ์คํฌ๋ฆฝํธ๋ฅผ <head> ์์ ๋๋ค๊ฐ ํ์ด์ง ๋ ๋๋ง์ด ๋์ ๋๊ฒ ๋๋ ค์ง๋ ๊ฑธ ๊ฒช๊ณ ๋์์ผ ๊ถ๊ธ์ฆ์ด ์๊ฒผ์์ด์.
"์ ๋๋ ธ์ง?"๋ผ๋ ์ง๋ฌธ ํ๋๊ฐ ๋ธ๋ผ์ฐ์ ๋ด๋ถ ๋ฉ์ปค๋์ฆ ์ ์ฒด๋ฅผ ์ดํดํ๊ฒ ๋ง๋ค์ด์คฌ์ด์.
์ด ๊ธ์ ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ฑํ๋ ๋ฐฉ์๋ถํฐ ๋ ์ด์์ ๋น์ฉ์ ๊ฑฐ์ณ ๋ฆฌ์กํธ์ ์ถ์ํ ์๋ฆฌ์ Fiber ์ค์ผ์ค๋ง๊น์ง ์ฐ๊ฒฐ๋๋ ํ๋ฆ์ ๋ค๋ค์.
๋ธ๋ผ์ฐ์ ๋์ ์๋ฆฌ๋ฅผ ์ฒ์ ํ๊ณ ๋๋ ๊ฐ๋ฐ์๋ ๋ฆฌ์กํธ ๋ด๋ถ ๋์์ด ๊ถ๊ธํ ๋ถ์๊ฒ ๋์์ด ๋ ๊ฑฐ์์.
ํ๋ ๋ธ๋ผ์ฐ์ ๋ ์ฑ๋ฅ์ ์ํด ๋ฉํฐ ํ๋ก์ธ์ค ์ํคํ ์ฒ๋ฅผ ์ฌ์ฉํ์ง๋ง ์ฐ๋ฆฌ๊ฐ ๋ณด๋ ์นํ์ด์ง ํ๋๋ ๋ณดํต ํ๋์ ๋ ๋๋ฌ ํ๋ก์ธ์ค(Renderer Process) ๋ด๋ถ์์ ๋์ํด์.
์ด ํ๋ก์ธ์ค ์์ ๋ฉ์ธ ์ค๋ ๋๋ ๋งค์ฐ ๋ฐ๋น ์.
(Chrome์ Blink)(Chrome์ V8)๋ ์์ง์ ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๊ณต์ ํด์. ํ์ชฝ์ด ์ค๋ ๋๋ฅผ ์ก๊ณ ์์ผ๋ฉด ๋ค๋ฅธ ์ชฝ์ ๊ธฐ๋ค๋ ค์ผ ํด์. ์ฑ๋ฅ ๋ฌธ์ ์ ์๋น์๊ฐ ๋ฐ๋ก ์ด ๋๊ธฐ ์๊ฐ์์ ๋ฐ์ํด์.
๋ธ๋ผ์ฐ์ ๋ HTML ํ์ผ์ ์ ๋ถ ๋ค์ด๋ก๋๋ฐ์ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ์ง ์์์. ๋คํธ์ํฌ ๋ฒํผ์ ๋ฐ์ดํฐ๊ฐ ๋ค์ด์ค๋ ๋๋ก ์ฆ์ ํ ํฐํ๋ฅผ ์์ํ๋ ์ ์ง์ ํ์ฑ(Incremental Parsing) ๋ฐฉ์์ ์จ์. ์ด ๋ฐฉ์์ด LCP(Largest Contentful Paint)์ ์ง๊ฒฐ๋ผ์.
ํ์ฑ ํ๋ฆ์ ์๋์ ๊ฐ์์.
<html>, <body> ๊ฐ์ ์๋ฏธ ์๋ ๋จ์๋ก ์ชผ๊ฐ์.๊ทธ๋ฐ๋ฐ ์ด ๊ณผ์ ์์ <script> ํ๊ทธ๋ฅผ ๋ง๋๋ ์๊ฐ ํ์ฑ์ด ๋ฉ์ถฐ์. ์ด์ ๋ ์๋ฐ์คํฌ๋ฆฝํธ์ "๊ฐ๋ ฅํ ๊ถํ" ๋๋ฌธ์ด์์. document.write() ๊ฐ์ ๋ช
๋ น์ผ๋ก ํ์ฑ ์ค์ธ HTML ํ๋ฆ์ ์ ํ๊ทธ๋ฅผ ์ฆ์์์ ์ฃผ์
ํ ์ ์์ด์. ๋ธ๋ผ์ฐ์ ๊ฐ ํ์ฑ์ ๊ณ์ํ๋ฉด์ JS๋ ๋์์ ์คํํ๋๋ฐ ๊ทธ JS๊ฐ ๋ฐฉ๊ธ ํ์ฑํ ๋
ธ๋๋ฅผ ์ญ์ ํ๊ฑฐ๋ ์ ๋
ธ๋๋ฅผ ๋ผ์ ๋ฃ์ผ๋ฉด ์ํ ๋ถ์ผ์น(Race Condition)์ ๋น ์ ธ์.
๊ฒฐ๊ตญ "CSS๋ JS ์คํ์ ๋ง๊ณ (์คํ์ผ ์ ๋ณด ์ฐธ์กฐ๋ฅผ ์ํด) JS๋ HTML ํ์ฑ์ ๋ง๋" ์ฐ์ ์ฐจ๋จ์ด ๋ฐ์ํด์. ์ด๋ฅผ Critical Rendering Path(CRP)์ ๋ณ๋ชฉ์ด๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
ํ์ฑ ์ฐจ๋จ์ ํผํ๋ ๋ฐฉ๋ฒ์ด async์ defer์ธ๋ฐ ๋์ด ํท๊ฐ๋ฆฌ๊ธฐ ์ฌ์์.
<!-- async: ๋ค์ด๋ก๋ ์๋ฃ ์ฆ์ ์คํ, ํ์ฑ ๋์ค์ ๋ผ์ด๋ค ์ ์์ -->
<script async src="analytics.js"></script>
<!-- defer: ๋ค์ด๋ก๋๋ ๋ณ๋ ฌ๋ก ํ๋, DOM ํ์ฑ์ด ๋๋ ๋ค์ ์์๋๋ก ์คํ -->
<script defer src="app.js"></script>
async๋ ๋ค์ด๋ก๋๊ฐ ๋๋๋ ์๊ฐ ๋ฐ๋ก ์คํ๋ผ์. ํ์ฑ ์ค์ด์ด๋ ๋ฉ์ถ๊ณ JS๋ฅผ ๋จผ์ ์คํํ๊ณ ์คํ ์์๋ ๋ณด์ฅํ์ง ์์์. ์๋ก ๋
๋ฆฝ์ ์ธ ์คํฌ๋ฆฝํธ(Google Analytics, ๊ด๊ณ ํ๊ทธ ๋ฑ)์ ์จ์ผ ํ๋ ์ด์ ์์.
defer๋ ๋ค์ด๋ก๋๋ฅผ ํ์ฑ๊ณผ ๋ณ๋ ฌ๋ก ์งํํ์ง๋ง ์คํ์ DOM์ด ์์ฑ๋ ๋ค๋ก ๋ฏธ๋ค์. ์ ์ธ ์์๋๋ก ์คํ๋ ๋ณด์ฅํด์. ์ค๋ฌด์์ ๊ธฐ๋ณธ๊ฐ์ defer๋ก ๋๊ณ async๋ ๋ค๋ฅธ ์คํฌ๋ฆฝํธ์ ์ ํ ๊ด๊ณ์๋ ๋
๋ฆฝ ์คํฌ๋ฆฝํธ์๋ง ์ ๋ณํด์ ์ฐ๋ ๊ฒ ์์ ํด์.
์ค๋ฌด์์ ๊ธฐ๋ณธ๊ฐ์ defer๋ก ๋๊ณ async๋ ๋ค๋ฅธ ์คํฌ๋ฆฝํธ์ ์ ํ ๊ด๊ณ์๋ ๋
๋ฆฝ ์คํฌ๋ฆฝํธ์๋ง ์ ๋ณํด์ ์ฐ๋ ๊ฒ ์์ ํด์.
HTML์ด DOM์ ๋ง๋ค ๋ CSS๋ CSSOM(CSS Object Model)์ ๋ง๋ค์ด์. CSSOM์ ํต์ฌ์ Cascade(์ฐ์ ์์ ๊ฒฐ์ ์๊ณ ๋ฆฌ์ฆ)์์. ๋ถ๋ชจ๋ก๋ถํฐ ๋ด๋ ค์ค๋ ๊ท์น๊ณผ ๋ช ์๋(Specificity) ์ ์ธ ์์๋ฅผ ์ข ํฉํด์ ์ต์ข ์คํ์ผ ๊ฐ์ ๊ฒฐ์ ํด์.
์คํดํ๊ธฐ ์ฌ์ด ๋ถ๋ถ์ด ์์ด์. CSSOM์ด ์์ฑ๋๋ ๋์ DOM ํ์ฑ ์์ฒด๋ ๋ฉ์ถ์ง ์์์. ๋ค๋ง ์์ฑ๋ DOM์ ํ๋ฉด์ ๊ทธ๋ฆฌ๊ธฐ ์ํ Render Tree ์์ฑ๊ณผ JS ์คํ์ CSSOM์ด ์์ฑ๋ ๋๊น์ง ์ฐจ๋จ๋ผ์. CSS๋ HTML ๊ตฌ์กฐ ํด์ ์์ฒด์ ๋ณ๋ชฉ์ด ์๋๋ผ ํ๋ฉด ์ถ๋ ฅ๊ณผ ์คํฌ๋ฆฝํธ ์คํ์ ๋ณ๋ชฉ์ด์์.
ํ๋ ๋ธ๋ผ์ฐ์ ๋ Preload Scanner๋ฅผ ํ์ฌํ๊ณ ์์ด์. ๋ฉ์ธ ํ์๊ฐ JS์ ๋งํ ๋ฉ์ถฐ์์ ๋ ๋ณ๋์ ๊ฐ๋ฒผ์ด ํ์๊ฐ ๋ค์ชฝ HTML์ ๋ฏธ๋ฆฌ ํ์ด์ <img>, <link>, <script> ๋ฆฌ์์ค๋ฅผ ์ฐพ์ ๋ฏธ๋ฆฌ ๋ค์ด๋ก๋ํด์.
Preload Scanner์ ํต์ฌ ํ๊ณ๋ ์ ์ ์ธ ๋งํฌ์ ๋ง ์ฝ๋๋ค๋ ๊ฑฐ์์. JS๋ก ๋์ ์ผ๋ก ์์ฑํ๋ ๋ฆฌ์์ค๋ ๋ฏธ๋ฆฌ ์ฐพ์๋ผ ์ ์์ด์.
<!-- Preload Scanner๊ฐ ๊ฐ์งํ ์ ์์ - ๋งํฌ์
์ ์ง์ ์จ์์ -->
<img src="hero.webp" alt="ํ์ด๋ก ์ด๋ฏธ์ง">
<!-- Preload Scanner๊ฐ ๊ฐ์ง ๋ชปํจ - JS๊ฐ ์คํ๋๊ธฐ ์ ๊น์ง ์กด์ฌํ์ง ์์ -->
<script>
const img = document.createElement('img');
img.src = 'hero.webp'; // ์ค์บ๋ ์
์ฅ์์ ๋ณด์ด์ง ์๋ ๋ฆฌ์์ค
document.body.appendChild(img);
</script>
JS๋ก ๋์ ์์ฑํ๋ ์ด๋ฏธ์ง๋ ํฐํธ๊ฐ LCP์ ์ํฅ์ ์ค๋ค๋ฉด <link rel="preload">๋ก ์ง์ ํํธ๋ฅผ ์ค์ผ ํด์.
<link rel="preload" href="hero-image.webp" as="image">
<script src="analytics.js" defer></script>
<link rel="stylesheet" href="critical.css">
preload๋ฅผ ๋จ๋ฐํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์ฐ์ ์์ ํ๋จ์ ํฌ๊ธฐํด์ ์ ์ ์ค์ํ ๋ฆฌ์์ค๊ฐ ๋ฆ๊ฒ ๋ถ๋ฌ์์ง๋ ์ญํจ๊ณผ๊ฐ ์๊ฒจ์. LCP์ ์ํฅ์ ์ฃผ๋ hero ์ด๋ฏธ์ง๋ ์ฒซ ํ๋ฉด์ ํ์ํ ํฐํธ ์ ๋์๋ง ์ ๋ณ์ ์ผ๋ก ์จ์ผ ํด์.
ํ์ฑ์ด ๋๋๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์ค์ ๋ก ํฝ์ ์ ์ฐ๋ ์์ ์ ์์ํด์.
๋ธ๋ผ์ฐ์ ๋ DOM ํธ๋ฆฌ ๋ฃจํธ๋ถํฐ ์ํํ๋ฉด์ ํ๋ฉด์ ์ค์ ๋ก ๋ณด์ด๋ ๋ ธ๋๋ค๋ง ๊ณจ๋ผ CSSOM ์คํ์ผ์ ์ ์ฉํ ๋ ๋ ํธ๋ฆฌ๋ฅผ ๋ง๋ค์ด์.
display: none โ ๋ ๋ ํธ๋ฆฌ์ ํฌํจ๋์ง ์์์visibility: hidden โ ๊ณต๊ฐ์ ์ฐจ์งํ๋๊น ํฌํจ๋ผ์::before, ::after ๊ฐ์ ์์๋ ์ด ๋จ๊ณ์์ ๋
ธ๋ํ๋ผ์| ๋จ๊ณ | ๋ฐ์ ์ ๋ฐ ์์ฑ (์์) | ๋น๊ณ |
|---|---|---|
| Reflow | width, height, margin, top, left | ์ฃผ๋ณ ์์๊น์ง ์ฌ๊ณ์ฐ (๋น์) |
| Repaint | color, background-color, visibility | ์๊ฐ์ ์์๋ง ๋ณ๊ฒฝ |
| Composite | transform, opacity | GPU ๊ฐ์ ํ์ฉ (๊ฐ์ฅ ์ ๋ ด) |
๋ ๋ ํธ๋ฆฌ๊ฐ ์์ฑ๋์ด๋ ๊ฐ ์์์ ์ ํํ ์์น์ ํฌ๊ธฐ๋ ์์ง ๋ชฐ๋ผ์. ๋ ์ด์์(Layout/Reflow) ๋จ๊ณ์์ ๋ทฐํฌํธ ํฌ๊ธฐ์ ๋ง์ถฐ ๊ณ์ฐํด์. %, em, vh ๊ฐ์ ์๋ ๋จ์๋ค์ด ์ด ๋จ๊ณ์์ ๋ฌผ๋ฆฌ์ ์ธ ํฝ์
๊ฐ์ผ๋ก ํ์ ๋ผ์.
๋ธ๋ผ์ฐ์ ๋ ์คํ์ผ ๋ณ๊ฒฝ์ ํ์ ์์๋๋ค๊ฐ ํ๊บผ๋ฒ์ ์ฒ๋ฆฌํ๋ ค๊ณ ํด์. ๊ทธ๋ฐ๋ฐ ์คํ์ผ์ ๋ฐ๊พผ ์งํ์ ํฌ๊ธฐ๋ ์์น๋ฅผ ์ฝ๋ ์ฝ๋๋ฅผ ์คํํ๋ฉด ๋ธ๋ผ์ฐ์ ๋ ์ฆ์ ๋ค์ ๊ณ์ฐํด์ผ ํด์. ์ด๊ฒ ๊ฐ์ ๋๊ธฐ ๋ ์ด์์(Forced Synchronous Layout)์ด์์.
๋ฃจํ ์์์ ์ด๊ฑธ ๋ฐ๋ณตํ๋ฉด ๋งค ๋ฐ๋ณต๋ง๋ค Reflow๊ฐ ํฐ์ง๋ Layout Thrashing์ด ๋ฐ์ํด์.
// ์ฑ๋ฅ์ ๋ง๊ฐ๋จ๋ฆฌ๋ ํจํด
function updateBad(elements) {
for (let i = 0; i < elements.length; i++) {
// offsetWidth๋ฅผ ์ฝ์ ๋๋ง๋ค Reflow ๊ฐ์ ๋ฐ์
elements[i].style.width = (container.offsetWidth + 10) + 'px';
}
}
// ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ ๋ถ๋ฆฌํ ํจํด
function updateGood(elements) {
const width = container.offsetWidth; // ํ ๋ฒ๋ง ์ฝ๊ธฐ
requestAnimationFrame(() => {
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = (width + 10) + 'px'; // ์ฐ๊ธฐ๋ง ๋ฐ๋ณต
}
});
}
ํ๋ ๋ธ๋ผ์ฐ์ ๋ ํ๋ฉด ์ ์ฒด๋ฅผ ํ๊บผ๋ฒ์ ๊ทธ๋ฆฌ์ง ์์์. ์ฌ๋ฌ ๊ฐ์ ๋ ์ด์ด(Layer)๋ก ๋๋ ์ ๊ทธ๋ฆฐ ๋ค ํฉ์ณ์(Composite).
top์ด๋ left๋ฅผ ๋ณ๊ฒฝํ๋ฉด Reflow โ Repaint โ Composite ์ ๊ณผ์ ์ด ๋ฉ์ธ ์ค๋ ๋์์ ์ผ์ด๋์. JS๊ฐ ๋ฐ์๋ฉด ์ ๋๋ฉ์ด์
์ด ๋๋ ๋๊ธฐ๋๋ฐ ์ด๊ฑธ Jank๋ผ๊ณ ํด์.
๋ฐ๋ฉด transform๊ณผ opacity๋ ํด๋น ์์๋ฅผ ๋ณ๋์ Composite Layer๋ก ์น๊ฒฉ์์ผ์. ์ด๋ฏธ ๊ทธ๋ ค์ง ๋นํธ๋งต ์ด๋ฏธ์ง๋ฅผ GPU๊ฐ ์ขํ๋ง ๋ฐ๊ฟ์ ์ฌ๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋ฉ์ธ ์ค๋ ๋์ ๋ฌด๊ดํ๊ฒ 60fps๋ฅผ ์ ์งํด์.
/* ๋ ์ด์ด ์น๊ฒฉ์ ๋ฏธ๋ฆฌ ํํธ๋ก ์๋ ค์ฃผ๋ ๋ฐฉ๋ฒ */
.animated-element {
will-change: transform;
}
/* transform์ผ๋ก ์ด๋ - GPU์์ ์ฒ๋ฆฌ, ๋ฉ์ธ ์ค๋ ๋ ๋ฌด๊ด */
.move-with-transform {
transform: translateX(100px);
}
/* left๋ก ์ด๋ - Reflow ๋ฐ์, ๋ฉ์ธ ์ค๋ ๋ ์ ์ */
.move-with-left {
left: 100px;
}
will-change๋ ๋จ์ฉํ๋ฉด ์ ๋ผ์. ๋ ์ด์ด๋ฅผ ๋ง๋๋ ๋ฐ ๋ฉ๋ชจ๋ฆฌ๊ฐ ํ์ํ๊ฑฐ๋ ์. ์ค์ ๋ก ์ ๋๋ฉ์ด์
์ด ์ผ์ด๋๋ ์์์๋ง ์จ์ผ ํด์. Reflow๋ฅผ ์ ๋ฐํ๋ CSS ์์ฑ์ ๋ฏธ๋ฆฌ ์์๋๋ฉด ์ข์์. width, height, margin, padding, top, left๋ ์ ๋ถ Reflow๋ฅผ ๋ฐ์์์ผ์. ๋ฐ๋ฉด color, background-color๋ Repaint๋ง ์ผ์ผํค๊ณ transform๊ณผ opacity๋ Composite๋ง ๊ฑด๋๋ ค์.
๋ธ๋ผ์ฐ์ ์์ค์์ ์ด ๋ชจ๋ ๊ฑธ ๊ฐ๋ฐ์๊ฐ ์ง์ ์ฑ๊ธฐ๋ ๊ฑด ๊ท๋ชจ๊ฐ ์ปค์ง๋ฉด ๋ถ๊ฐ๋ฅ์ ๊ฐ๊น์์. ๋ฆฌ์กํธ์ ํต์ฌ ๊ฐ์น๋ ์ฑ๋ฅ์ ์ต์ ํํ์ ์ ์๋์ผ๋ก ์ง์ผ์ฃผ๋ ๊ฒ์ด์์.
"๋ ๋๋ง"์ด๋ผ๋ ๋จ์ด๋ฅผ ๋ฆฌ์กํธ์ ๋ธ๋ผ์ฐ์ ๊ฐ ์์ ํ ๋ค๋ฅธ ์๋ฏธ๋ก ์จ์.
๋ฆฌ์กํธ ๋ ๋๋ง์ด ์ผ์ด๋ฌ๋ค๊ณ ํด์ ๋ฐ๋์ ๋ธ๋ผ์ฐ์ ๋ ๋๋ง(Reflow, Repaint)์ด ์ผ์ด๋๋ ๊ฑด ์๋์์. ์ด ๊ตฌ๋ถ์ด ์ต์ ํ ํฌ์ธํธ๋ฅผ ์ฐพ๋ ๋ฐ ํต์ฌ์ด์์.
๊ฐ์ ๋(Virtual DOM)์ ์ค์ DOM์ด ์๋ ๋ ธ๋ ์ ๋ณด๋ฅผ ๋ด์ ๊ฐ๋ฒผ์ด JS ๊ฐ์ฒด์์.
// ๊ฐ์ ๋ ๊ฐ์ฒด์ ์ค์ ์๊น์
{
type: 'div',
props: {
className: 'container',
style: { color: 'red' }
},
children: [
{
type: 'h1',
props: { id: 'title' },
children: ['์๋
ํ์ธ์']
}
]
}
์ด JS ๊ฐ์ฒด๋ฅผ ์ด์ ๊ฒ๊ณผ ๋น๊ต(Diffing)ํด์ ๋ฐ๋ ๋ถ๋ถ๋ง ์ฐพ์๋ด๊ณ ๊ทธ ์ฐจ์ด์ ๋ง ์ค์ DOM์ ๋ฑ ํ ๋ฒ ๋ฐ์(Patching)ํด์.
๋ ํธ๋ฆฌ๋ฅผ ์์ ํ ๋น๊ตํ๋ฉด ์๋ O(nยณ)์ ๋น์ฉ์ด ๋ค์ด์. ๋ ธ๋ ์๊ฐ 1000๊ฐ๋ฉด 10์ต ๋ฒ์ ๋น๊ต๊ฐ ํ์ํ ์ ์ด์์. ๋ฆฌ์กํธ๋ ๋ ๊ฐ์ง ํด๋ฆฌ์คํฑ์ผ๋ก ์ด ๋น์ฉ์ O(n)์ผ๋ก ์ค์์ด์.
<div>๊ฐ <section>์ผ๋ก ๋ฐ๋๋ฉด ๊ทธ ์์ ์์๋ค์ ์ผ์ผ์ด ๋น๊ตํ์ง ์๊ณ ์ ๋ถ ๋ฒ๋ฆฌ๋ ๊ฑฐ์์.๊ฐ์ ๋์ด ์ค์ DOM๋ณด๋ค ํญ์ ๋น ๋ฅธ ๊ฑด ์๋์์. ์ฐ์ฐ์ ํ ๊ฒน ๋ ์น๋ ๊ฑฐ๋ผ์ ๋จ์ํ ๋ณ๊ฒฝ์ด๋ผ๋ฉด ์ง์ DOM ์กฐ์์ด ๋ ๋น ๋ฅผ ์ ์์ด์. ํ์ง๋ง ๋ฆฌ์กํธ์ ์ง๊ฐ๋ "๋๊ฐ ์ฝ๋๋ฅผ ์ง๋๋ผ๋ ์ฑ๋ฅ์ ์ต์ ํํ์ ์ ์ง์ผ์ฃผ๋ ๊ฒ"์ ์์ด์.
Key ๊ฐ์ ์์ ์ด์ด์ง๋ ์๊ธฐ์ธ๋ฐ์. ๋ฆฌ์กํธ๋ Key๋ฅผ ๋ณด๊ณ ๋
ธ๋๋ฅผ ์ฌ์ฌ์ฉํ ์ง ๊ฒฐ์ ํด์. key={index}๋ฅผ ์ฐ๋ฉด ๋ฐฐ์ด ์ค๊ฐ์ ์์ดํ
์ด ์ถ๊ฐ๋ ๋ ์ธ๋ฑ์ค๊ฐ ๋ฐ๋ฆฌ๊ณ ๋ฆฌ์กํธ๋ ๋ฉ์ฉกํ ๋
ธ๋๋ฅผ ์ ๋ถ ์
๋ฐ์ดํธํด์. ๋ ์ฌ๊ฐํ ๊ฑด Input ๊ฐ์ ๋ด๋ถ ์ํ๊ฐ ์๋ฑํ ์์น๋ก ๋ถ์ด๋ค๋๋ ๋ฒ๊ทธ์์. ๋ฐ์ดํฐ ๋ถ์ผ์น ๋ฒ๊ทธ ์ค์ ์์ธ ์ฐพ๊ธฐ ์ ์ผ ์ด๋ ค์ด ๋ถ๋ฅ๊ฐ ์ฌ๊ธฐ์ ๋์์.
Key๋ ์ ์ญ ์ ์ผํ์ง ์์๋ ๋ผ์. ๊ฐ์ ๋ฐฐ์ด ์์์๋ง ์ ์ผํ๋ฉด ๋ผ์. UUID๋ฅผ ๋งค๋ฒ ์๋ก ์์ฑํด์ key๋ก ์ฐ๋ฉด ๋ ๋๋ง๋ง๋ค key๊ฐ ๋ฐ๋์ด์ ๋ฆฌ์กํธ๊ฐ ํญ์ ์ ๋ ธ๋๋ก ์ธ์ํ๊ณ ์ฌ์ฌ์ฉ์ ๋ชป ํด์.
Key๋งํผ์ด๋ ์ค์ํ ๊ฒ ๋ฆฌ์กํธ๊ฐ DOM์ ์ธ์ ์ฝ๊ณ ์ฐ๋๋์์. ํ ์ ์คํ ํ์ด๋ฐ์ ์๋ชป ์ก์ผ๋ฉด ๋ธ๋ผ์ฐ์ Paint์ ์ถฉ๋ํด์ ๊น๋นก์(Flickering)์ด ์๊ฒจ์.
// useEffect: Paint๊ฐ ๋๋ ๋ค ๋น๋๊ธฐ ์คํ โ ๊น๋นก์ ๋ฐ์ ๊ฐ๋ฅ
useEffect(() => {
const rect = element.getBoundingClientRect();
setTooltipPosition(rect); // ํ๋ฉด ๊ทธ๋ฆฐ ํ ์คํ โ ์์น ์ฌ๊ณ์ฐ์ผ๋ก ๊น๋นก์
}, []);
// useLayoutEffect: Paint ์ง์ ์ ๋๊ธฐ ์คํ โ ๊น๋นก์ ์์
useLayoutEffect(() => {
const rect = element.getBoundingClientRect();
setTooltipPosition(rect); // Paint ์ง์ ์คํ โ ์ฌ์ฉ์ ๋์ ๋ณด์ด์ง ์์
}, []);
useLayoutEffect๋ ๋๊ธฐ๋ก ์คํ๋๋๊น ๋ฌด๊ฑฐ์ด ์์ ์ ์ฌ๊ธฐ์ ํ๋ฉด Paint๊ฐ ๋ธ๋กํน๋ผ์. DOM ์์น ์ธก์ ์ด๋ ์ฆ๊ฐ์ ์ธ ์คํ์ผ ๋๊ธฐํ์ฒ๋ผ ์ ๋ง ํ์ํ ๊ฒฝ์ฐ์๋ง ์จ์ผ ํด์.
5000๊ฐ ์์ดํ ๋ฆฌ์คํธ๋ฅผ ๋ ๋๋งํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น์? ๊ณผ๊ฑฐ ๋ฆฌ์กํธ(Stack Reconciler)๋ ์ฌ์กฐ์ ์ด ์์๋๋ฉด ๋ฉ์ถ ์ ์์์ด์. ์ปดํฌ๋ํธ ํธ๋ฆฌ๋ฅผ ์ฝ ์คํ์ ๋ฐ๋ผ ์ฌ๊ท์ ์ผ๋ก ๋ด๋ ค๊ฐ๋ ๊ตฌ์กฐ์๊ธฐ ๋๋ฌธ์ ๋ ๋๋ง ๋์ค์ ๋ฉ์ธ ์ค๋ ๋๋ฅผ ๊ฝ ์ก๊ณ ์์ด์ ์ฌ์ฉ์ ์ ๋ ฅ์ด ์นํ๋ ํ๋ฆฌ์ง(Freezing) ํ์์ด ์๊ฒผ์ด์. ์ฌ๋์ด ํ์ดํํ ๋ 100ms ์ด์ ๋ฐ์์ด ์์ผ๋ฉด "์ฑ์ด ์ฃฝ์๋?"๋ผ๊ณ ๋๊ปด์.
Fiber๋ ๊ทธ ํ๊ณ๋ฅผ ๊ทผ๋ณธ์ ์ผ๋ก ๋ฏ์ด๊ณ ์น ๋ด๋ถ ์ฌ์ค๊ณ์์.
Stack Reconciler์ ๊ทผ๋ณธ ๋ฌธ์ ๋ ์ฌ๊ท ํธ์ถ์ด์์. ์ฌ๊ท๋ ์ค๊ฐ์ ๋ฉ์ถ ์ ์์ด์. Fiber๋ ์ด ๊ตฌ์กฐ๋ฅผ ์ฐ๊ฒฐ ๋ฆฌ์คํธ(Linked List)๋ก ๋ฐ๊ฟจ์ด์.
// Fiber ๋
ธ๋์ ๊ฐ๋
์ ๊ตฌ์กฐ
{
type: MyComponent,
stateNode: ์ปดํฌ๋ํธ_์ธ์คํด์ค,
return: ๋ถ๋ชจ_Fiber, // ์๋ก ์ฌ๋ผ๊ฐ๋ ํฌ์ธํฐ
child: ์์_Fiber, // ์๋๋ก ๋ด๋ ค๊ฐ๋ ํฌ์ธํฐ
sibling: ํ์ _Fiber, // ์์ผ๋ก ๊ฐ๋ ํฌ์ธํฐ
pendingProps: {},
memoizedState: {}
}
๊ฐ Fiber ๋ ธ๋๊ฐ ๋ค์ ๋ ธ๋์ ํฌ์ธํฐ๋ฅผ ๊ฐ์ง๋๊น ์ค๊ฐ์ ์์ ์ ๋ฉ์ถ๊ณ ํฌ์ธํฐ๋ง ์ ์ฅํด๋๋ฉด ๋์ค์ ์ ํํ ๊ทธ ์ง์ ๋ถํฐ ์ฌ๊ฐํ ์ ์์ด์.
Fiber๋ ๋ ๋๋ง์ ์์ ๋จ์๋ก ์ชผ๊ฐ์ ๊ด๋ฆฌํด์.
๋ฆฌ์กํธ๊ฐ ๊ณ์ฐ ์ค์ธ ์ด์คํ ์ํ๋ฅผ ์ฌ์ฉ์์๊ฒ ๋คํค์ง ์๋ ๋น๊ฒฐ์ ๋ฌด์์ผ๊น์? ๋ฆฌ์กํธ๋ ๋ง์น ์ฐ๊ทน ๋ฌด๋๋ฅผ ๊ต์ฒดํ๋ฏ ๋ ๊ฐ์ ํธ๋ฆฌ๋ฅผ ๊ด๋ฆฌํ๋ '๋๋ธ ๋ฒํผ๋ง' ์ ๋ต์ ์ฌ์ฉํด์.
์ด ๊ณผ์ ์ ๋ค์๊ณผ ๊ฐ์ ๋จ๊ณ๋ก ์ ๊ตํ๊ฒ ์งํ๋ฉ๋๋ค
๋ฌด๋ ๋ค์์ ์ค๋น (Render Phase): ๋ ๋๋ง ๋ด๋ด ๋ฆฌ์กํธ๋ ์ค์ง WIP ํธ๋ฆฌ๋ง ์์ ํด์. ์ด๋ ์ค์ ํ๋ฉด์ธ Current ํธ๋ฆฌ๋ ์ ํ ๊ฑด๋๋ฆฌ์ง ์๊ณ ๊ทธ๋๋ก ์ ์งํ์ฃ .
์์ฑ ํ ํ ๋ฒ์ ๊ต์ฒด (Commit Phase): ๋ชจ๋ ๊ณ์ฐ๊ณผ ์ํ ๋ฐฐ์น๊ฐ ์๋ฒฝํ ๋๋๋ฉด ๋ฆฌ์กํธ๋ ๋จ ํ ๋ฒ์ ์กฐ์์ผ๋ก ๋ ๋ฌด๋์ ์์น๋ฅผ ์์๊ฐ์ ๋ฐ๊ฟ์(Swap).
์์์ (Atomic) ์คํ: ์ด ๊ต์ฒด ์์ ์ '์ ๋ถ ์๋๋ฉด ์ ๋ฌด(All or Nothing)'๋ก ์ฒ๋ฆฌ๋์. ๋๋ถ์ ์ฌ์ฉ์๋ ์ํ์ด ํ๋์ฉ ๋ฐฐ์น๋๋ ๊ณผ์ ์์ด ๋ ์์ฑ๋ ํ๋ ์๋ง ๋ณด๊ฒ ๋์.
Git์์feature๋ธ๋์น ์์ ์ ๋ง์น๊ณmain์ ํ ๋ฒ์ ๋จธ์งํ๋ ๊ฒ๊ณผ ๊ฐ์ ์๋ฆฌ์์.
const [isPending, startTransition] = useTransition();
function handleInput(e) {
// ์ฆ๊ฐ ๋ฐ์์ด ํ์ํ ์์
(Urgent)
setInputValue(e.target.value);
// ์กฐ๊ธ ๋ฆ์ด๋ ๋๋ ์์
(Transition)
startTransition(() => {
setSearchResults(heavyFilter(e.target.value));
});
}
startTransition ์์ ์์
์ ํ์ดํ ์ค์ ์ ์
๋ ฅ์ด ๋ค์ด์ค๋ฉด ๊ธฐ์กด ๊ณ์ฐ์ ํ๊ธฐํ๊ณ ์
๋ ฅ์ ๋จผ์ ๋ฐ์ํด์. "๋น ๋ฅธ ๋ ๋๋ง"์ด ์๋๋ผ "์ฐ์ ์์์ ๋ฐ๋ฅธ ์ ํ์ ๋ ๋๋ง"์ด์์.
componentWillMount๊ฐ Unsafe ํ์ ์ ๋ฐ์ ์ด์ ๋ ์ฌ๊ธฐ์ ์์ด์. Fiber์์ Render Phase๋ ์ธ์ ๋ ๋ฉ์ถ๊ฑฐ๋ ๋ฒ๋ ค์ง๊ฑฐ๋ ๋ค์ ์์๋ ์ ์์ด์. componentWillMount์์ API๋ฅผ ํธ์ถํ๋ฉด ์ฌ๋ฌ ๋ฒ ์คํ๋ ์ ์๋ ๋ถ์์ฉ์ด ์๊ฒจ์. ๋ถ์ ํจ๊ณผ๋ ๋ฐ๋์ Commit Phase ์ดํ์ธ useEffect์์ ์ฒ๋ฆฌํด์ผ ํด์.
๋ธ๋ผ์ฐ์ ํ์ฑ ์ฐจ๋จ๋ถํฐ Fiber ์ค์ผ์ค๋ง๊น์ง ์ญ ์ด์ด์ง๋ ์ด ํ๋ฆ์ ํต์ฌ์ ๊ฒฐ๊ตญ ํ๋์์.
๋ฉ์ธ ์ค๋ ๋๋ฅผ ์ผ๋ง๋ ์๊ฒ ์ชผ๊ฐ์ ์ฌ์ฉ์์๊ฒ ์ ์ด๊ถ์ ๋๋ ค์ฃผ๋๋๊ฐ ์ฑ๋ฅ ์ต์ ํ์ ๋ณธ์ง์ด์์.
๊ฐ๋
๋ง ์ฝ๊ณ ๋์ด๊ฐ๋ฉด ๊ธ๋ฐฉ ์ํ๋๋ฐ Chrome DevTools Performance ํญ์ ์ด์ด์ ์ง์ ํ์๋ผ์ธ์ ์ฐ์ด๋ณด๋ฉด ๋ฌ๋ผ์ ธ์.
๋
นํ ํ ํ์๋ผ์ธ์์ ๋ณด๋ผ์ ๋ธ๋ก์ด Layout, ์ด๋ก์์ด Paint, startTransition ์ ์ฉ ์ ํ๋ก Long Task(๋นจ๊ฐ ์ผ๊ฐํ)๊ฐ ์ฌ๋ผ์ง๋ ๊ฑธ ๋์ผ๋ก ๋ณด๋ฉด ์ด ๊ธ์์ ๋ค๋ฃฌ ๊ฐ๋
๋ค์ด ํ์๋ผ์ธ ์ ์ด๋์ ํด๋นํ๋์ง ๋ฐ๋ก ๋ณด์ฌ์.