Ver3.0 ๐ DOM ์กฐ์๊ณผ ๊ฐ์ DOM์ ์ฑ๋ฅ ์ฐจ์ด ์๋ฒฝ ๋ถ์: ์น ๊ฐ๋ฐ์๋ฅผ ์ํ ์ต์ ํ ์ ๋ต ๊ฐ์ด๋

๐ DOM ์กฐ์๊ณผ ๊ฐ์ DOM์ ์ฑ๋ฅ ์ฐจ์ด ์๋ฒฝ ๋ถ์: ์น ๊ฐ๋ฐ์๋ฅผ ์ํ ์ต์ ํ ์ ๋ต ๊ฐ์ด๋
์ค์ ์์ ๋ฐ๋ก ์จ๋จน๋ DOM ์ฑ๋ฅ ์ต์ ํ ๋ ธํ์ฐ ๐ก
์์งํ ๋งํ๋ฉด, ์ฒ์ JavaScript ๋ฐฐ์ธ ๋๋ "๊ทธ๋ฅ document.getElementById() ์ฐ๋ฉด ๋๋ ๊ฑฐ ์๋?"๋ผ๊ณ ์๊ฐํ์์ด. ๊ทผ๋ฐ ํ๋ก์ ํธ ๊ท๋ชจ๊ฐ ์ปค์ง๊ณ , ์ฌ์ฉ์๊ฐ ๋์ด๋๋ฉด์ ์ฑ๋ฅ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ธฐ ์์ํ๋๋ผ๊ณ . ํ์ด์ง๊ฐ ๋ฒ๋ฒ ๊ฑฐ๋ฆฌ๊ณ , ์ฌ์ฉ์ ๊ฒฝํ์ด ๋จ์ด์ง๋ ๊ฑธ ๋ณด๋ฉด์ "์, ์ด๊ฒ ๋ฌธ์ ๊ตฌ๋!"๋ผ๊ณ ๊นจ๋ฌ์์ง.
React, Vue ๊ฐ์ ํ๋ ์์ํฌ๋ค์ด ์ ๊ฐ์ DOM์ ์ฌ์ฉํ๋์ง, ์ค์ ๋ก ์ฑ๋ฅ ์ฐจ์ด๊ฐ ์ผ๋ง๋ ๋๋์ง, ๊ทธ๋ฆฌ๊ณ ์ฐ๋ฆฌ๊ฐ ์ค๋ฌด์์ ์ด๋ป๊ฒ ์ต์ ํํ ์ ์๋์ง ์น๊ตฌํํ ์ค๋ช ํ๋ฏ์ด ํธํ๊ฒ ํ์ด๋ณผ๊ฒ! ๐ฏ
๐ญ DOM์ด ๋ญ๋ฐ? ๊ธฐ๋ณธ๋ถํฐ ์ ๋๋ก ์์๋ณด์
<div id="container">
<h1>์๋
ํ์ธ์</h1>
<p>DOM ๊ณต๋ถ ์ค์
๋๋ค</p>
</div>
๋ธ๋ผ์ฐ์ ๋ ์ด๊ฑธ ์ฝ๊ณ DOM ํธ๋ฆฌ๋ฅผ ๋ง๋ค์ด. ๊ทธ๋ฌ๋ฉด ์ฐ๋ฆฌ๋ JavaScript๋ก ์ด๋ ๊ฒ ์ ๊ทผํ ์ ์์ง:
const container = document.getElementById('container');
const heading = container.querySelector('h1');
heading.textContent = '๋ฐ๊ฐ์ต๋๋ค'; // ํ
์คํธ ๋ณ๊ฒฝ
heading.style.color = 'blue'; // ์คํ์ผ ๋ณ๊ฒฝ
๐ DOM ์กฐ์์ด ์ ๋๋ฆด๊น?
์ฌ๊ธฐ์ ํต์ฌ ์ง๋ฌธ! "๊ทธ๋ฅ ํ ์คํธ ๋ฐ๊พธ๋ ๊ฑด๋ฐ ๋ญ๊ฐ ๋๋ ค?" ๐คทโโ๏ธ์ฌ์ค DOM ์์ฒด๋ ๊ทธ๋ ๊ฒ ๋๋ฆฌ์ง ์์. ๋ฌธ์ ๋ DOM์ ๋ณ๊ฒฝํ์ ๋ ์ผ์ด๋๋ ํ์ ์์ ๋ค์ด์ผ. ๋ธ๋ผ์ฐ์ ๊ฐ ํด์ผ ํ๋ ์ผ๋ค์ ์์๋๋ก ๋ณด๋ฉด:
๋ณ๊ฒฝ๋ ์์์ ๊ด๋ จ๋ CSS ๊ท์น๋ค์ ๋ค์ ๊ณ์ฐํด.
2. Layout (Reflow)
์์์ ํฌ๊ธฐ๋ ์์น๊ฐ ๋ณ๊ฒฝ๋๋ฉด, ์ํฅ๋ฐ๋ ๋ชจ๋ ์์๋ค์ ์์น๋ฅผ ๋ค์ ๊ณ์ฐํด์ผ ํด. ์ด๊ฒ ์ ์ผ ๋ฌด๊ฑฐ์ด ์์ ์ด์ผ! ๐ฐ
3. Paint (Repaint)
ํ๋ฉด์ ์ค์ ๋ก ํฝ์ ์ ๊ทธ๋ฆฌ๋ ์์ . ์์, ๊ทธ๋ฆผ์, ํ ์คํธ ๋ฑ์ ๋ ๋๋งํด.
4. Composite
์ฌ๋ฌ ๋ ์ด์ด๋ฅผ ํฉ์ฑํด์ ์ต์ข ํ๋ฉด์ ๋ง๋ค์ด.
โ ๏ธ ์ฑ๋ฅ ํฌ๋ฌ: Reflow๋ฅผ ์ ๋ฐํ๋ ์์ ๋ค
์ค์ ๋ก ์ด๋ค ์์ ๋ค์ด Reflow๋ฅผ ์ผ์ผํค๋์ง ์์๋ณด์:| ์์ ์ ํ | ์์ ์ฝ๋ | ์ํฅ๋ |
|---|---|---|
| ํฌ๊ธฐ ๋ณ๊ฒฝ | element.style.width = '500px' | โ ๏ธโ ๏ธโ ๏ธ ๋์ |
| ์์น ๋ณ๊ฒฝ | element.style.top = '100px' | โ ๏ธโ ๏ธโ ๏ธ ๋์ |
| ํด๋์ค ์ถ๊ฐ/์ ๊ฑฐ | element.classList.add('active') | โ ๏ธโ ๏ธ ์ค๊ฐ |
| ๋ด์ฉ ๋ณ๊ฒฝ | element.innerHTML = '...' | โ ๏ธโ ๏ธ ์ค๊ฐ |
| ๋ ์ด์์ ์์ฑ ์ฝ๊ธฐ | element.offsetHeight | โ ๏ธโ ๏ธโ ๏ธ ๋์ (๊ฐ์ ๋๊ธฐํ) |
// ๐ซ ๋์ ์: ๋ฐ๋ณต๋ฌธ ์์์ ๋ ์ด์์ ์์ฑ ์ฝ๊ธฐ
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = elements[i].offsetWidth + 10 + 'px';
// offsetWidth๋ฅผ ์ฝ์ ๋๋ง๋ค ๊ฐ์ ๋ก ๋ ์ด์์ ๊ณ์ฐ ๋ฐ์!
}
// โ
์ข์ ์: ๋จผ์ ์ฝ๊ณ , ๋์ค์ ์ฐ๊ธฐ
const widths = [];
for (let i = 0; i < elements.length; i++) {
widths[i] = elements[i].offsetWidth; // ์ฝ๊ธฐ ๋จ๊ณ
}
for (let i = 0; i < elements.length; i++) {
elements[i].style.width = widths[i] + 10 + 'px'; // ์ฐ๊ธฐ ๋จ๊ณ
}
์ด๋ฐ ์์ผ๋ก ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ ๋ถ๋ฆฌํ๋ฉด Reflow ํ์๋ฅผ ํ๊ธฐ์ ์ผ๋ก ์ค์ผ ์ ์์ด! ๐ฏ
โจ ๊ฐ์ DOM์ ๋ฑ์ฅ: ๊ฒ์ ์ฒด์ธ์
๐จ ๊ฐ์ DOM์ ์ด๋ป๊ฒ ์๋ํ ๊น?
๋จ๊ณ๋ณ๋ก ์์ธํ ์์๋ณด์:์ค์ DOM์ ๊ฐ๋ฒผ์ด JavaScript ๊ฐ์ฒด ๋ฒ์ ์ ๋ง๋ค์ด. ์ด๊ฑด ๊ทธ๋ฅ ์ผ๋ฐ ๊ฐ์ฒด๋ผ์ ์กฐ์์ด ์์ฒญ ๋นจ๋ผ!
Step 2: ์ํ ๋ณ๊ฒฝ ๊ฐ์ง ๐
๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด, ์๋ก์ด ๊ฐ์ DOM ํธ๋ฆฌ๋ฅผ ๋ง๋ค์ด.
Step 3: Diffing ์๊ณ ๋ฆฌ์ฆ ์คํ ๐
์ด์ ๊ฐ์ DOM๊ณผ ์๋ก์ด ๊ฐ์ DOM์ ๋น๊ตํด์ ์ ํํ ์ด๋ค ๋ถ๋ถ์ด ๋ฐ๋์๋์ง ์ฐพ์๋ด.
Step 4: ์ต์ํ์ ๋ณ๊ฒฝ์ฌํญ๋ง ์ค์ DOM์ ๋ฐ์ โก
๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง ๊ณจ๋ผ์ ์ค์ DOM์ ์ ๋ฐ์ดํธํด. ์ด๊ฑธ "Reconciliation(์ฌ์กฐ์ )"์ด๋ผ๊ณ ๋ถ๋ฌ.
// React ์ปดํฌ๋ํธ
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
// todos ๋ฐฐ์ด์ด ๋ณ๊ฒฝ๋๋ฉด?
// 1. ์๋ก์ด ๊ฐ์ DOM ํธ๋ฆฌ ์์ฑ
// 2. ์ด์ ํธ๋ฆฌ์ ๋น๊ต (Diffing)
// 3. ๋ณ๊ฒฝ๋ <li> ์์๋ง ์ค์ DOM์์ ์
๋ฐ์ดํธ!
๋ง์ฝ 10๊ฐ์ todo ์ค์์ ํ๋๋ง ๋ฐ๋์๋ค๋ฉด? ๊ฐ์ DOM์ ๊ทธ ํ๋๋ง ์ค์ DOM์์ ์
๋ฐ์ดํธํด. ๋๋จธ์ง 9๊ฐ๋ ๊ฑด๋๋ฆฌ์ง๋ ์์! ์ด๊ฒ ๋ฐ๋ก ํจ์จ์ฑ์ ๋น๋ฐ์ด์ผ. ๐ฏ
๐ฌ Diffing ์๊ณ ๋ฆฌ์ฆ ๊น์ด ํํค์น๊ธฐ
React์ Diffing ์๊ณ ๋ฆฌ์ฆ์ ์ ๋ง ๋๋ํด. ๋ช ๊ฐ์ง ํด๋ฆฌ์คํฑ(๊ฒฝํ์ ๊ท์น)์ ์ฌ์ฉํด์ O(nยณ) ๋ณต์ก๋๋ฅผ O(n)์ผ๋ก ์ค์๊ฑฐ๋ ! ๐ฒํต์ฌ ํด๋ฆฌ์คํฑ 3๊ฐ์ง:
2. key prop์ ํตํด ์์ ์์์ ๋ณ๊ฒฝ์ ์ถ์ ํ๋ค
๋ฆฌ์คํธ ๋ ๋๋งํ ๋ key๋ฅผ ์ฐ๋ผ๊ณ ํ๋ ์ด์ ๊ฐ ๋ฐ๋ก ์ด๊ฑฐ์ผ. key๊ฐ ์์ผ๋ฉด ์ด๋ค ์์๊ฐ ์ถ๊ฐ/์ญ์ /์ด๋ํ๋์ง ์ ํํ ์ ์ ์์ด!
3. ๊ฐ์ ๋ ๋ฒจ์ ์์๋ค๋ผ๋ฆฌ๋ง ๋น๊ตํ๋ค
ํธ๋ฆฌ์ ๋ค๋ฅธ ๋ ๋ฒจ์ ์๋ ์์๋ค์ ๋น๊ตํ์ง ์์. ์ด๊ฒ ์ฑ๋ฅ์ ํฌ๊ฒ ํฅ์์์ผ!
// ๐ซ ๋์ ์: key ์์ด ๋ฆฌ์คํธ ๋ ๋๋ง
{items.map(item => (
<div>{item.name}</div>
))}
// ํญ๋ชฉ์ด ์ถ๊ฐ/์ญ์ ๋๋ฉด ๋ชจ๋ ์์๋ฅผ ๋ค์ ๋ ๋๋ง!
// โ
์ข์ ์: key ์ฌ์ฉ
{items.map(item => (
<div key={item.id}>{item.name}</div>
))}
// ๋ณ๊ฒฝ๋ ํญ๋ชฉ๋ง ์ ํํ ์
๋ฐ์ดํธ!
// ๐ซ ์ต์
์ ์: index๋ฅผ key๋ก ์ฌ์ฉ
{items.map((item, index) => (
<div key={index}>{item.name}</div>
))}
// ์์๊ฐ ๋ฐ๋๋ฉด ๋ชจ๋ ์์๊ฐ ๋ค์ ๋ ๋๋ง๋ ์ ์์ด!
๐ ์ค์ ์ฑ๋ฅ ๋น๊ต: ์ซ์๋ก ๋ณด๋ ์ฐจ์ด
๐งช ํ ์คํธ ์๋๋ฆฌ์ค
1000๊ฐ์ ๋ฆฌ์คํธ ์์ดํ ์ ๋ ๋๋งํ๊ณ , ๊ทธ ์ค 100๊ฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ํฉ์ ๊ฐ์ ํด๋ณผ๊ฒ. ์ค๋ฌด์์ ํํ ๋ณผ ์ ์๋ ์๋๋ฆฌ์ค์ง!| ๋ฐฉ์ | ์ด๊ธฐ ๋ ๋๋ง | ์ ๋ฐ์ดํธ (100๊ฐ) | ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ |
|---|---|---|---|
| ์์ DOM ์กฐ์ | ~45ms | ~120ms | ๋ฎ์ |
| jQuery | ~50ms | ~130ms | ์ค๊ฐ |
| React (Virtual DOM) | ~60ms | ~25ms | ์ค๊ฐ-๋์ |
| Vue 3 (Virtual DOM) | ~55ms | ~22ms | ์ค๊ฐ |
| Svelte (์ปดํ์ผ) | ~40ms | ~20ms | ๋ฎ์ |
๐ก ๊ฒฐ๊ณผ ๋ถ์
์ฌ๋ฏธ์๋ ํจํด์ด ๋ณด์ด์ง? ๐์ ๋ฐ์ดํธ ์ฑ๋ฅ
์ฌ๊ธฐ์ ๊ฐ์ DOM์ ์ง๊ฐ๊ฐ ๋๋ฌ๋! React์ Vue๋ ๋ณ๊ฒฝ๋ 100๊ฐ๋ง ์ ํํ ์ฐพ์์ ์ ๋ฐ์ดํธํด. ๋ฐ๋ฉด ์์ DOM ์กฐ์์ ๋นํจ์จ์ ์ธ ๋ฐฉ์์ผ๋ก ์ ๋ฐ์ดํธํ๋ฉด ํจ์ฌ ๋๋ ค์ง ์ ์์ด.
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ
๊ฐ์ DOM์ ๋ฉ๋ชจ๋ฆฌ์ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ ์งํด์ผ ํ๋๊น ์ถ๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํด. ํ์ง๋ง ์์ฆ ๋๋ฐ์ด์ค์์๋ ํฐ ๋ฌธ์ ๊ฐ ์ ๋ผ!
๐ฏ ์ค์ ๋ฒค์น๋งํฌ ์ฝ๋
์ง์ ํ ์คํธํด๋ณผ ์ ์๋ ์ฝ๋๋ฅผ ๊ณต์ ํ ๊ฒ:// ์์ DOM ์กฐ์ ๋ฐฉ์
function pureDOM_update() {
const start = performance.now();
const list = document.getElementById('list');
for (let i = 0; i < 100; i++) {
const item = list.children[i];
item.textContent = `Updated ${i}`;
item.style.backgroundColor = '#f0f0f0';
}
const end = performance.now();
console.log(`Pure DOM: ${end - start}ms`);
}
// React ๋ฐฉ์ (๊ฐ์ DOM)
function ReactComponent() {
const [items, setItems] = useState(initialItems);
const updateItems = () => {
const start = performance.now();
setItems(prevItems =>
prevItems.map((item, i) =>
i < 100 ? { ...item, text: `Updated ${i}`, bg: '#f0f0f0' } : item
)
);
// React๋ ๋น๋๊ธฐ๋ก ์
๋ฐ์ดํธํ๋ฏ๋ก useEffect์์ ์ธก์
requestAnimationFrame(() => {
const end = performance.now();
console.log(`React: ${end - start}ms`);
});
};
return (
<ul>
{items.map(item => (
<li key={item.id} style={{backgroundColor: item.bg}}>
{item.text}
</li>
))}
</ul>
);
}
์ค์ ๋ก ๋๋ ค๋ณด๋ฉด ์ฐจ์ด๋ฅผ ์ฒด๊ฐํ ์ ์์ ๊ฑฐ์ผ! ํนํ ์
๋ฐ์ดํธ ํ์๊ฐ ๋ง์์ง์๋ก ๊ฐ์ DOM์ ์ฅ์ ์ด ๋์ฑ ๋๋๋ฌ์ ธ. ๐
๐ ๏ธ ์ค์ ์ต์ ํ ์ ๋ต: DOM ์กฐ์ ํธ
1๏ธโฃ DocumentFragment ํ์ฉํ๊ธฐ
์ฌ๋ฌ ๊ฐ์ ์์๋ฅผ ์ถ๊ฐํ ๋ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์ด์ผ!// ๐ซ ๋์ ์: ๋ฐ๋ณต๋ฌธ์์ ์ง์ DOM์ ์ถ๊ฐ
const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
list.appendChild(li); // 1000๋ฒ์ Reflow ๋ฐ์!
}
// โ
์ข์ ์: DocumentFragment ์ฌ์ฉ
const list = document.getElementById('list');
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li); // ๋ฉ๋ชจ๋ฆฌ์์๋ง ์์
}
list.appendChild(fragment); // ๋จ 1๋ฒ์ Reflow!
์ฑ๋ฅ ํฅ์: ์ฝ 10~20๋ฐฐ! ์ง์ง ํฐ ์ฐจ์ด์ผ. ๐ฒ
2๏ธโฃ ๋ฐฐ์น ์คํ์ผ ๋ณ๊ฒฝ
์คํ์ผ์ ์ฌ๋ฌ ๋ฒ ๋ณ๊ฒฝํ ๋๋ ํ ๋ฒ์ ๋ชจ์์ ์ ์ฉํ์:// ๐ซ ๋์ ์: ๊ฐ๋ณ ์คํ์ผ ๋ณ๊ฒฝ
element.style.width = '100px'; // Reflow
element.style.height = '100px'; // Reflow
element.style.border = '1px solid black'; // Reflow
// โ
์ข์ ์ 1: cssText ์ฌ์ฉ
element.style.cssText = 'width: 100px; height: 100px; border: 1px solid black;';
// โ
์ข์ ์ 2: ํด๋์ค ์ฌ์ฉ (๊ฐ์ฅ ๊ถ์ฅ!)
element.className = 'styled-box';
// CSS
// .styled-box {
// width: 100px;
// height: 100px;
// border: 1px solid black;
// }
ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด ๊ฐ์ฅ ์ข์. CSS๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์ต์ ํํ๊ธฐ ์ฝ๊ฑฐ๋ ! ๐จ
3๏ธโฃ ์์๋ฅผ ์จ๊ธฐ๊ณ ์์ ํ๊ธฐ
๋ณต์กํ DOM ์กฐ์์ ํ ๋๋ ์์๋ฅผ ์ผ์์ ์ผ๋ก ์จ๊ธฐ๋ ๊ฒ ํจ๊ณผ์ ์ด์ผ:const element = document.getElementById('complex-element');
// ์์๋ฅผ ์จ๊น (display: none์ ๋ ์ด์์์์ ์ ๊ฑฐ)
element.style.display = 'none';
// ๋ณต์กํ DOM ์กฐ์ ์ํ
for (let i = 0; i < 100; i++) {
// ์ฌ๋ฌ ์์ ์์ ์ถ๊ฐ/์์
// ์ด ๋์์๋ Reflow๊ฐ ๋ฐ์ํ์ง ์์!
}
// ๋ค์ ํ์
element.style.display = 'block'; // ์ด๋ ํ ๋ฒ๋ง Reflow
4๏ธโฃ ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋ถ๋ฆฌํ๊ธฐ
์์์๋ ์ธ๊ธํ์ง๋ง, ์ ๋ง ์ค์ํด์ ๋ค์ ํ๋ฒ! ๐// ๐ซ ๋์ ์: ์ฝ๊ธฐ์ ์ฐ๊ธฐ๊ฐ ์์ฌ์์
const elements = document.querySelectorAll('.item');
elements.forEach(el => {
const width = el.offsetWidth; // ์ฝ๊ธฐ โ ๊ฐ์ Reflow
el.style.width = width * 1.5 + 'px'; // ์ฐ๊ธฐ
const height = el.offsetHeight; // ์ฝ๊ธฐ โ ๊ฐ์ Reflow
el.style.height = height * 1.5 + 'px'; // ์ฐ๊ธฐ
});
// โ
์ข์ ์: ์ฝ๊ธฐ์ ์ฐ๊ธฐ ๋ถ๋ฆฌ
const elements = document.querySelectorAll('.item');
const dimensions = [];
// 1๋จ๊ณ: ๋ชจ๋ ์ฝ๊ธฐ ์์
elements.forEach(el => {
dimensions.push({
width: el.offsetWidth,
height: el.offsetHeight
});
});
// 2๋จ๊ณ: ๋ชจ๋ ์ฐ๊ธฐ ์์
elements.forEach((el, i) => {
el.style.width = dimensions[i].width * 1.5 + 'px';
el.style.height = dimensions[i].height * 1.5 + 'px';
});
5๏ธโฃ ์ด๋ฒคํธ ์์ (Event Delegation)
์๋ฐฑ ๊ฐ์ ์์์ ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ๋ฌ ๋ ํ์ ๊ธฐ๋ฒ์ด์ผ!// ๐ซ ๋์ ์: ๊ฐ ์์์ ๊ฐ๋ณ ๋ฆฌ์ค๋
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', handleClick);
});
// ๋ฉ๋ชจ๋ฆฌ ๋ญ๋น + ๋์ ์์ ์ฒ๋ฆฌ ์ด๋ ค์
// โ
์ข์ ์: ๋ถ๋ชจ์ ํ๋์ ๋ฆฌ์ค๋
const list = document.getElementById('list');
list.addEventListener('click', (e) => {
if (e.target.classList.contains('item')) {
handleClick(e);
}
});
// ๋ฉ๋ชจ๋ฆฌ ํจ์จ์ + ๋์ ์์ ์๋ ์ฒ๋ฆฌ!
์ด๋ฒคํธ ์์์ ์ฑ๋ฅ๋ฟ๋ง ์๋๋ผ ์ฝ๋ ์ ์ง๋ณด์์๋ ์ข์. ๋์ค์ ์์๋ฅผ ์ถ๊ฐํด๋ ์๋์ผ๋ก ์ด๋ฒคํธ๊ฐ ์๋ํ๊ฑฐ๋ ! ๐ฏ
์ ๋๋ฉ์ด์ ์ด๋ ์คํฌ๋กค ์ด๋ฒคํธ ์ฒ๋ฆฌํ ๋๋ requestAnimationFrame์ ์ฌ์ฉํ์:
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
requestAnimationFrame(() => {
// ์คํฌ๋กค ์ฒ๋ฆฌ ๋ก์ง
updateScrollPosition();
ticking = false;
});
ticking = true;
}
});
๋ธ๋ผ์ฐ์ ์ ๋ฆฌํ์ธํธ ์ฃผ๊ธฐ์ ๋ง์ถฐ์ ์คํ๋๋๊น ํจ์ฌ ๋ถ๋๋ฌ์! ๐ฌ
6๏ธโฃ Debounce์ Throttle
์ด๋ฒคํธ๊ฐ ๋๋ฌด ์์ฃผ ๋ฐ์ํ๋ ๊ฑธ ์ ์ดํ๋ ๊ธฐ๋ฒ์ด์ผ:// Debounce: ๋ง์ง๋ง ํธ์ถ ํ ์ผ์ ์๊ฐ์ด ์ง๋์ผ ์คํ
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
// ์ฌ์ฉ ์: ๊ฒ์ ์
๋ ฅ
const searchInput = document.getElementById('search');
const debouncedSearch = debounce((value) => {
// API ํธ์ถ ๋ฑ
console.log('Searching for:', value);
}, 300);
searchInput.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
// Throttle: ์ผ์ ์๊ฐ๋ง๋ค ํ ๋ฒ๋ง ์คํ
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// ์ฌ์ฉ ์: ์คํฌ๋กค ์ด๋ฒคํธ
const throttledScroll = throttle(() => {
console.log('Scroll position:', window.scrollY);
}, 100);
window.addEventListener('scroll', throttledScroll);
์ธ์ ๋ญ ์ธ๊น?- Debounce: ๊ฒ์ ์ ๋ ฅ, ์๋์ฐ ๋ฆฌ์ฌ์ด์ฆ (๋ง์ง๋ง ๋์๋ง ์ค์ํ ๋)
- Throttle: ์คํฌ๋กค, ๋ง์ฐ์ค ์ด๋ (์ผ์ ๊ฐ๊ฒฉ์ผ๋ก ๊ณ์ ์คํํด์ผ ํ ๋)
โก ์ค์ ์ต์ ํ ์ ๋ต: ๊ฐ์ DOM ํธ
1๏ธโฃ React ์ต์ ํ ๊ธฐ๋ฒ
React.memo๋ก ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ๋ฐฉ์ง// ๐ซ ์ต์ ํ ์ : ๋ถ๋ชจ๊ฐ ๋ฆฌ๋ ๋๋ง๋๋ฉด ์์๋ ๋ฌด์กฐ๊ฑด ๋ฆฌ๋ ๋๋ง
function ChildComponent({ name, age }) {
console.log('Child rendered!');
return <div>{name} ({age})</div>;
}
// โ
์ต์ ํ ํ: props๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ๋ฆฌ๋ ๋๋ง
const ChildComponent = React.memo(({ name, age }) => {
console.log('Child rendered!');
return <div>{name} ({age})</div>;
});
// ๋ ์ธ๋ฐํ ์ ์ด๊ฐ ํ์ํ๋ฉด ๋ ๋ฒ์งธ ์ธ์ ์ฌ์ฉ
const ChildComponent = React.memo(
({ name, age }) => {
return <div>{name} ({age})</div>;
},
(prevProps, nextProps) => {
// true๋ฅผ ๋ฐํํ๋ฉด ๋ฆฌ๋ ๋๋ง ์คํต
return prevProps.name === nextProps.name &&
prevProps.age === nextProps.age;
}
);
useMemo์ useCallback ํ์ฉ
function ExpensiveComponent({ data }) {
// ๐ซ ๋์ ์: ๋งค๋ฒ ์ฌ๊ณ์ฐ
const processedData = processLargeData(data);
// โ
์ข์ ์: data๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ฌ๊ณ์ฐ
const processedData = useMemo(
() => processLargeData(data),
[data]
);
// ๐ซ ๋์ ์: ๋งค๋ฒ ์๋ก์ด ํจ์ ์์ฑ
const handleClick = () => {
console.log('Clicked!');
};
// โ
์ข์ ์: ํจ์ ์ฌ์ฌ์ฉ
const handleClick = useCallback(() => {
console.log('Clicked!');
}, []); // ์์กด์ฑ ๋ฐฐ์ด์ด ๋น์ด์์ผ๋ฉด ํ ๋ฒ๋ง ์์ฑ
return <ChildComponent data={processedData} onClick={handleClick} />;
}
// react-window ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ์์
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
// 10,000๊ฐ ์์ดํ
๋ ๋ถ๋๋ฝ๊ฒ ์คํฌ๋กค!
ํ๋ฉด์ ๋ณด์ด๋ ์์ดํ
๋ง ๋ ๋๋งํ๋๊น ์ฑ๋ฅ์ด 100๋ฐฐ ์ด์ ํฅ์๋ ์ ์์ด! ๐ฒ
2๏ธโฃ Vue ์ต์ ํ ๊ธฐ๋ฒ
v-once์ v-memo ๋๋ ํฐ๋ธ<!-- v-once: ํ ๋ฒ๋ง ๋ ๋๋ง, ์ดํ ์
๋ฐ์ดํธ ๋ฌด์ -->
<div v-once>
<h1>{{ staticTitle }}</h1>
<p>{{ staticContent }}</p>
</div>
<!-- v-memo: ์กฐ๊ฑด๋ถ ๋ฉ๋ชจ์ด์ ์ด์
(Vue 3.2+) -->
<div v-memo="[user.id, user.name]">
<!-- user.id๋ user.name์ด ๋ณ๊ฒฝ๋ ๋๋ง ์
๋ฐ์ดํธ -->
<UserProfile :user="user" />
</div>
Computed vs Methods
export default {
data() {
return {
items: [/* ๋ง์ ๋ฐ์ดํฐ */]
};
},
// ๐ซ ๋์ ์: methods๋ ๋งค๋ฒ ์คํ
methods: {
filteredItems() {
return this.items.filter(item => item.active);
}
},
// โ
์ข์ ์: computed๋ ์บ์ฑ๋จ
computed: {
filteredItems() {
return this.items.filter(item => item.active);
}
}
}
// ํ
ํ๋ฆฟ์์
<div v-for="item in filteredItems" :key="item.id">
{{ item.name }}
</div>
computed๋ ์์กดํ๋ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง ์ฌ๊ณ์ฐ๋๋๊น ํจ์ฌ ํจ์จ์ ์ด์ผ! ๐
3๏ธโฃ ๊ณตํต ์ต์ ํ ์ ๋ต
Key ์ต์ ํ// ๐ซ ์ต์
: key ์์
<div v-for="item in items">{{ item.name }}</div>
// ๐ซ ๋์จ: index๋ฅผ key๋ก ์ฌ์ฉ
<div v-for="(item, index) in items" :key="index">
{{ item.name }}
</div>
// โ
์ข์: ๊ณ ์ ํ ID ์ฌ์ฉ
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
// โ
์ต๊ณ : ๋ณตํฉ key (ํ์ํ ๊ฒฝ์ฐ)
<div v-for="item in items" :key="`${item.category}-${item.id}`">
{{ item.name }}
</div>
์กฐ๊ฑด๋ถ ๋ ๋๋ง ์ต์ ํ
<!-- v-if vs v-show -->
<!-- v-if: DOM์์ ์์ ํ ์ ๊ฑฐ/์ถ๊ฐ (ํ ๊ธ ๋น์ฉ ๋์) -->
<div v-if="isVisible">์์ฃผ ์ ๋ฐ๋๋ ๋ด์ฉ</div>
<!-- v-show: display ์์ฑ๋ง ๋ณ๊ฒฝ (ํ ๊ธ ๋น์ฉ ๋ฎ์) -->
<div v-show="isVisible">์์ฃผ ๋ฐ๋๋ ๋ด์ฉ</div>
<!-- ๊ท์น: ์์ฃผ ํ ๊ธ๋๋ฉด v-show, ์๋๋ฉด v-if -->
- ์ด๊ธฐ ๋ ๋๋ง ๋น์ฉ์ ์ค์ด๊ณ ์ถ์ ๋
- ์กฐ๊ฑด์ด ์์ฃผ ๋ณํ์ง ์์ ๋
- ์กฐ๊ฑด๋ถ๋ก ๋ง์ ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ ๋
v-show๋ฅผ ์ฌ์ฉํ ๋:
- ํ ๊ธ์ด ๋น๋ฒํ ๋ (ํญ, ๋ชจ๋ฌ ๋ฑ)
- ์ด๊ธฐ ๋ ๋๋ง ๋น์ฉ์ด ์ค์ํ์ง ์์ ๋
- ๊ฐ๋จํ ์์์ผ ๋
๐ง ๊ฐ๋ฐ์ ๋๊ตฌ๋ก ์ฑ๋ฅ ์ธก์ ํ๊ธฐ
1๏ธโฃ Chrome DevTools Performance ํญ
2. Performance ํญ ์ ํ
3. ๋ นํ ๋ฒํผ(โ) ํด๋ฆญ
4. ์ธก์ ํ๊ณ ์ถ์ ๋์ ์ํ
5. ์ ์ง ๋ฒํผ ํด๋ฆญ
6. ๊ฒฐ๊ณผ ๋ถ์! ๐
| ์งํ | ์๋ฏธ | ๋ชฉํ |
|---|---|---|
| FPS | ์ด๋น ํ๋ ์ ์ | 60 FPS ์ ์ง |
| Scripting | JavaScript ์คํ ์๊ฐ | ์ต์ํ |
| Rendering | ๋ ์ด์์, ํ์ธํธ ์๊ฐ | ์ต์ํ |
| Painting | ํฝ์ ๊ทธ๋ฆฌ๊ธฐ ์๊ฐ | ์ต์ํ |
| Idle | ์ ํด ์๊ฐ | ๋ง์์๋ก ์ข์ |
2๏ธโฃ React DevTools Profiler
React ์ฑ์ด๋ผ๋ฉด ํ์ ๋๊ตฌ์ผ!// ํ๋กํ์ผ๋ง ์ฝ๋ ์ถ๊ฐ
import { Profiler } from 'react';
function onRenderCallback(
id, // ํ๋กํ์ผ๋ฌ ID
phase, // "mount" ๋๋ "update"
actualDuration, // ๋ ๋๋ง์ ๊ฑธ๋ฆฐ ์๊ฐ
baseDuration, // ๋ฉ๋ชจ์ด์ ์ด์
์์ด ๊ฑธ๋ฆด ์๊ฐ
startTime, // ๋ ๋๋ง ์์ ์๊ฐ
commitTime, // ์ปค๋ฐ ์๊ฐ
interactions // ์ด ์
๋ฐ์ดํธ์ ๊ด๋ จ๋ ์ํธ์์ฉ
) {
console.log(`${id} took ${actualDuration}ms to render`);
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<YourComponent />
</Profiler>
);
}
3๏ธโฃ Lighthouse ์ฌ์ฉํ๊ธฐ
์ ์ฒด์ ์ธ ์ฑ๋ฅ ์ ์๋ฅผ ํ์ธํ๊ณ ์ถ๋ค๋ฉด Lighthouse!2. Lighthouse ํญ ์ ํ
3. "Generate report" ํด๋ฆญ
4. ์ฑ๋ฅ, ์ ๊ทผ์ฑ, SEO ๋ฑ ์ข ํฉ ์ ์ ํ์ธ! ๐ฏ
ํนํ Performance ์ ์์ Opportunities ์น์ ์ ์ฃผ๋ชฉํ์. ๊ตฌ์ฒด์ ์ธ ๊ฐ์ ๋ฐฉ๋ฒ์ ์ ์ํด์ค!
4๏ธโฃ ์ปค์คํ ์ฑ๋ฅ ์ธก์
์ง์ ์ฝ๋์์ ์ฑ๋ฅ์ ์ธก์ ํ ์๋ ์์ด:// Performance API ์ฌ์ฉ
const start = performance.now();
// ์ธก์ ํ๊ณ ์ถ์ ์ฝ๋
for (let i = 0; i < 1000; i++) {
document.getElementById('list').appendChild(createItem(i));
}
const end = performance.now();
console.log(`Execution time: ${end - start}ms`);
// Performance Observer ์ฌ์ฉ (๋ ์ ๊ตํ ์ธก์ )
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.duration}ms`);
}
});
observer.observe({ entryTypes: ['measure'] });
performance.mark('start-render');
// ๋ ๋๋ง ์ฝ๋
performance.mark('end-render');
performance.measure('render-time', 'start-render', 'end-render');
๐ ์ค์ ์ผ์ด์ค ์คํฐ๋
๐ฑ ์ผ์ด์ค 1: ๋ฌดํ ์คํฌ๋กค ํผ๋ ์ต์ ํ
1. ๋ชจ๋ ์์ดํ ์ด DOM์ ๋จ์์์ (500๊ฐ โ 5000๊ฐ๋ก ์ฆ๊ฐ)
2. ๊ฐ ์์ดํ ๋ง๋ค ์ด๋ฏธ์ง ๋ก๋ฉ
3. ์คํฌ๋กค ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ๋๋ฌด ์์ฃผ ์คํ๋จ
ํด๊ฒฐ ๋ฐฉ๋ฒ:
// 1. ๊ฐ์ ์คํฌ๋กค ๊ตฌํ (react-window ์ฌ์ฉ)
import { VariableSizeList } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
function FeedList({ items, loadMore, hasMore }) {
const itemSizes = useRef({});
const getItemSize = (index) => {
return itemSizes.current[index] || 200; // ๊ธฐ๋ณธ ๋์ด
};
const setItemSize = (index, size) => {
itemSizes.current[index] = size;
};
return (
<InfiniteLoader
isItemLoaded={(index) => index < items.length}
itemCount={hasMore ? items.length + 1 : items.length}
loadMoreItems={loadMore}
>
{({ onItemsRendered, ref }) => (
<VariableSizeList
height={window.innerHeight}
itemCount={items.length}
itemSize={getItemSize}
onItemsRendered={onItemsRendered}
ref={ref}
width="100%"
>
{({ index, style }) => (
<FeedItem
item={items[index]}
style={style}
onResize={(height) => setItemSize(index, height)}
/>
)}
</VariableSizeList>
)}
</InfiniteLoader>
);
}
// 2. ์ด๋ฏธ์ง Lazy Loading
function FeedItem({ item, style, onResize }) {
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // ์ค์ ์ด๋ฏธ์ง ๋ก๋
observer.unobserve(img);
}
});
},
{ rootMargin: '50px' } // 50px ์ ์ ๋ฏธ๋ฆฌ ๋ก๋
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<div style={style}>
<img
ref={imgRef}
data-src={item.imageUrl}
alt={item.title}
onLoad={(e) => onResize(e.target.height)}
/>
<p>{item.content}</p>
</div>
);
}
// 3. ์คํฌ๋กค ์ด๋ฒคํธ ์ต์ ํ
const handleScroll = useCallback(
throttle(() => {
// ์คํฌ๋กค ์ฒ๋ฆฌ ๋ก์ง
}, 100),
[]
);
๊ฒฐ๊ณผ:- ์ด๊ธฐ ๋ ๋๋ง ์๊ฐ: 2.5์ด โ 0.3์ด (์ฝ 8๋ฐฐ ๊ฐ์ !) ๐
- ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋: 500MB โ 50MB (10๋ฐฐ ๊ฐ์!)
- ์คํฌ๋กค FPS: 30 โ 60 (๋ถ๋๋ฌ์ 2๋ฐฐ ํฅ์!)
๐ ์ผ์ด์ค 2: ๋์ฉ๋ ๋ฐ์ดํฐ ํ ์ด๋ธ ์ต์ ํ
// 1. ์๋ฒ ์ฌ์ด๋ ํ์ด์ง๋ค์ด์
+ ํด๋ผ์ด์ธํธ ๊ฐ์ํ
function OrderTable() {
const [data, setData] = useState([]);
const [page, setPage] = useState(0);
const pageSize = 50;
// ์๋ฒ์์ ํ์ํ ๋ฐ์ดํฐ๋ง ๊ฐ์ ธ์ค๊ธฐ
const fetchData = async (pageNum) => {
const response = await fetch(
`/api/orders?page=${pageNum}&size=${pageSize}`
);
const newData = await response.json();
setData(newData);
};
// ๊ฐ์ ์คํฌ๋กค ์ ์ฉ
return (
<VirtualTable
data={data}
columns={columns}
rowHeight={50}
onPageChange={fetchData}
/>
);
}
// 2. ์ ๋ ฌ/ํํฐ๋ง ์ต์ ํ
function useOptimizedFilter(data, filterConfig) {
return useMemo(() => {
// Web Worker๋ก ๋ฌด๊ฑฐ์ด ํํฐ๋ง ์์
์ด๋
const worker = new Worker('filter-worker.js');
return new Promise((resolve) => {
worker.postMessage({ data, filterConfig });
worker.onmessage = (e) => {
resolve(e.data);
worker.terminate();
};
});
}, [data, filterConfig]);
}
// filter-worker.js
self.onmessage = function(e) {
const { data, filterConfig } = e.data;
const filtered = data.filter(item => {
// ๋ณต์กํ ํํฐ๋ง ๋ก์ง
return matchesFilter(item, filterConfig);
});
self.postMessage(filtered);
};
๊ฒฐ๊ณผ:- ์ด๊ธฐ ๋ก๋ฉ: 15์ด โ 1์ด
- ํํฐ๋ง ์๋: 5์ด โ 0.5์ด
- ๋ฉ๋ชจ๋ฆฌ: 1GB โ 100MB
- ์ฌ์ฉ์ ๋ง์กฑ๋: ๐๐๐
๐ฎ ์ผ์ด์ค 3: ์ค์๊ฐ ๋์๋ณด๋ ์ต์ ํ
// 1. ์ปดํฌ๋ํธ ๋ถ๋ฆฌ ๋ฐ ๋ฉ๋ชจ์ด์ ์ด์
const Dashboard = () => {
const [realtimeData, setRealtimeData] = useState([]);
return (
<>
{/* ์ค์๊ฐ ์
๋ฐ์ดํธ๋๋ ๋ถ๋ถ๋ง ๋ถ๋ฆฌ */}
<RealtimeChart data={realtimeData} />
{/* ์ ์ ์ธ ๋ถ๋ถ์ ๋ฉ๋ชจ์ด์ ์ด์
*/}
<StaticStats />
<StaticFilters />
</>
);
};
// 2. ๋ฐ์ดํฐ ์
๋ฐ์ดํธ ๋ฐฐ์น ์ฒ๋ฆฌ
function useRealtimeData(socketUrl) {
const [data, setData] = useState([]);
const bufferRef = useRef([]);
useEffect(() => {
const socket = new WebSocket(socketUrl);
socket.onmessage = (event) => {
// ์ฆ์ ์
๋ฐ์ดํธํ์ง ์๊ณ ๋ฒํผ์ ์ ์ฅ
bufferRef.current.push(JSON.parse(event.data));
};
// 100ms๋ง๋ค ํ ๋ฒ์ฉ ๋ฐฐ์น ์
๋ฐ์ดํธ
const interval = setInterval(() => {
if (bufferRef.current.length > 0) {
setData(prev => [...prev, ...bufferRef.current]);
bufferRef.current = [];
}
}, 100);
return () => {
socket.close();
clearInterval(interval);
};
}, [socketUrl]);
return data;
}
// 3. ์ฐจํธ ๋ ๋๋ง ์ต์ ํ
const RealtimeChart = React.memo(({ data }) => {
// Canvas ์ฌ์ฉ (SVG๋ณด๋ค ๋น ๋ฆ)
const canvasRef = useRef();
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
// requestAnimationFrame์ผ๋ก ๋ถ๋๋ฌ์ด ์ ๋๋ฉ์ด์
let animationId;
const render = () => {
drawChart(ctx, data);
animationId = requestAnimationFrame(render);
};
render();
return () => cancelAnimationFrame(animationId);
}, [data]);
return <canvas ref={canvasRef} />;
}, (prev, next) => {
// ๋ฐ์ดํฐ๊ฐ ์ค์ ๋ก ๋ณ๊ฒฝ๋์์ ๋๋ง ๋ฆฌ๋ ๋๋ง
return prev.data.length === next.data.length;
});
๊ฒฐ๊ณผ:- CPU ์ฌ์ฉ๋ฅ : 80% โ 20%
- ํ๋ ์ ๋๋กญ: ์์ฃผ ๋ฐ์ โ ๊ฑฐ์ ์์
- ๋ฐฐํฐ๋ฆฌ ์๋ชจ: 50% ๊ฐ์ (๋ชจ๋ฐ์ผ)
- ์ค์๊ฐ์ฑ ์ ์งํ๋ฉด์๋ ๋ถ๋๋ฌ์ด UX ๋ฌ์ฑ! โจ
๐ ํ๋ ์์ํฌ๋ณ ํน์ง๊ณผ ์ ํ ๊ฐ์ด๋
| ํ๋ ์์ํฌ | ๊ฐ์ DOM | ์ฅ์ | ์ ํฉํ ๊ฒฝ์ฐ |
|---|---|---|---|
| React | โ ์ฌ์ฉ | ๊ฑฐ๋ํ ์ํ๊ณ, ํ๋ถํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ๋์ ์ฑ์ฉ ์์ | ๋๊ท๋ชจ ํ๋ก์ ํธ, ํ ํ์ , ๋ณต์กํ UI |
| Vue 3 | โ ์ฌ์ฉ | ๋ฐฐ์ฐ๊ธฐ ์ฌ์, ์ ์ฐํจ, ์ข์ ๋ฌธ์, ํ๊ตญ์ด ์ง์ | ์ค์๊ท๋ชจ ํ๋ก์ ํธ, ๋น ๋ฅธ ๊ฐ๋ฐ, ์ ์ง์ ๋์ |
| Svelte | โ ์ปดํ์ผ | ๋ฒ๋ค ํฌ๊ธฐ ์์, ๋น ๋ฅธ ์ฑ๋ฅ, ๊ฐ๊ฒฐํ ๋ฌธ๋ฒ | ์ฑ๋ฅ์ด ์ค์ํ ํ๋ก์ ํธ, ์์ ๋ฒ๋ค ํฌ๊ธฐ ํ์ |
| Solid | โ ์ธ๋ฐํ ๋ฐ์์ฑ | ์ต๊ณ ์์ค์ ์ฑ๋ฅ, React์ ์ ์ฌํ ๋ฌธ๋ฒ | ๊ทนํ์ ์ฑ๋ฅ์ด ํ์ํ ๊ฒฝ์ฐ |
| Vanilla JS | โ ์์ | ์์กด์ฑ ์์, ์์ ํ ์ ์ด, ์์ ํฌ๊ธฐ | ๊ฐ๋จํ ํ๋ก์ ํธ, ํ์ต ๋ชฉ์ , ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ |
๐ฏ ์ ํ ๊ธฐ์ค
โ Vanilla JS ๋๋ Svelte
์ด์ : ์ค๋ฒํค๋ ์ต์ํ, ๋น ๋ฅธ ๋ก๋ฉ
์ค๊ท๋ชจ (๊ธฐ์ ์น์ฌ์ดํธ, ๊ด๋ฆฌ์ ํ์ด์ง)
โ Vue 3 ๋๋ React
์ด์ : ์์ฐ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๊ท ํ
๋๊ท๋ชจ (๋ณต์กํ SPA, ์ํฐํ๋ผ์ด์ฆ ์ฑ)
โ React ๋๋ Vue 3
์ด์ : ํ๋ถํ ์ํ๊ณ, ๊ฒ์ฆ๋ ์ํคํ ์ฒ ํจํด
์ฑ๋ฅ์ด ์ต์ฐ์ (๊ฒ์, ์ค์๊ฐ ์ฑ)
โ Svelte ๋๋ Solid
์ด์ : ์ต์ํ์ ๋ฐํ์ ์ค๋ฒํค๋
๐ผ ์ค๋ฌด ๊ด์ ์์์ ๊ณ ๋ ค์ฌํญ
ํ๋ ์์ํฌ ์ ํํ ๋ ๊ธฐ์ ์ ์ฑ๋ฅ๋ง ๋ณด๋ฉด ์ ๋ผ. ์ด๋ฐ ๊ฒ๋ค๋ ์ค์ํด:2. ์ฑ์ฉ ์์ฅ
React ๊ฐ๋ฐ์๊ฐ ๊ฐ์ฅ ๋ง๊ณ , Vue๋ ๊ฝค ์์ด. Svelte๋ ์์ง ์ ์ ํธ.
3. ์ํ๊ณ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋์ง ํ์ธํด๋ด. React๋ ๊ฑฐ์ ๋ชจ๋ ๊ฒ ์์ด!
4. ์ฅ๊ธฐ ์ ์ง๋ณด์
5๋ ํ์๋ ์ง์๋ ๊น? ์ปค๋ฎค๋ํฐ๊ฐ ํ๋ฐํ ๊น?
5. ํ์ต ๊ณก์
๋นจ๋ฆฌ ๋ฐฐ์์ผ ํ๋ค๋ฉด Vue๊ฐ ์ ๋ฆฌํด. React๋ ๊ฐ๋ ์ด ์ข ๋ ๋ง์.
๐ ๋ฏธ๋์ DOM ์ต์ ํ ๊ธฐ์
1๏ธโฃ ์๋ฒ ์ปดํฌ๋ํธ (React Server Components)
React 18์์ ๋์ ๋ ํ์ ์ ์ธ ๊ฐ๋ ์ด์ผ!// ์๋ฒ ์ปดํฌ๋ํธ (Server Component)
// ์๋ฒ์์๋ง ์คํ๋๊ณ , ํด๋ผ์ด์ธํธ๋ก HTML๋ง ์ ์ก
async function ProductList() {
// ์๋ฒ์์ ์ง์ DB ์กฐํ ๊ฐ๋ฅ!
const products = await db.products.findMany();
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ (Client Component)
'use client'; // ์ด ์ง์์ด๋ก ๊ตฌ๋ถ
function AddToCartButton({ productId }) {
const [isAdding, setIsAdding] = useState(false);
// ํด๋ผ์ด์ธํธ์์๋ง ์คํ๋๋ ์ธํฐ๋ํฐ๋ธ ๋ก์ง
const handleClick = () => {
setIsAdding(true);
// ...
};
return <button onClick={handleClick}>Add to Cart</button>;
}
์ฅ์ :- JavaScript ๋ฒ๋ค ํฌ๊ธฐ ๋ํญ ๊ฐ์ ๐ฆ
- ์๋ฒ ๋ฆฌ์์ค ์ง์ ์ ๊ทผ ๊ฐ๋ฅ
- ์ด๊ธฐ ๋ก๋ฉ ์๋ ํฅ์
- SEO ๊ฐ์
2๏ธโฃ Islands Architecture (์์ผ๋๋ ์ํคํ ์ฒ)
Astro ๊ฐ์ ํ๋ ์์ํฌ๊ฐ ์ฌ์ฉํ๋ ๊ฐ๋ ์ด์ผ.์๋ฅผ ๋ค์ด, ๋ธ๋ก๊ทธ ํฌ์คํธ๋ ์ ์ HTML์ด๊ณ , ๋๊ธ ์น์ ๋ง React ์ปดํฌ๋ํธ๋ก ๋ง๋๋ ๊ฑฐ์ผ.
---
// Astro ์์
import Header from '../components/Header.astro'; // ์ ์
import BlogPost from '../components/BlogPost.astro'; // ์ ์
import Comments from '../components/Comments.jsx'; // ์ธํฐ๋ํฐ๋ธ
---
<html>
<Header />
<BlogPost />
<!-- ์ด ๋ถ๋ถ๋ง JavaScript ๋ก๋ -->
<Comments client:load />
</html>
๊ฒฐ๊ณผ:- JavaScript ๋ฒ๋ค: 500KB โ 50KB
- ์ด๊ธฐ ๋ก๋ฉ: 3์ด โ 0.5์ด
- Lighthouse ์ ์: 70 โ 95
3๏ธโฃ Resumability (์ฌ๊ฐ ๊ฐ๋ฅ์ฑ)
Qwik ํ๋ ์์ํฌ๊ฐ ์ ์ํ๋ ์๋ก์ด ํจ๋ฌ๋ค์์ด์ผ.2. ํด๋ผ์ด์ธํธ๋ก ์ ์ก
3. JavaScript ๋ค์ด๋ก๋
4. ์ ์ฒด ์ฑ์ ๋ค์ ์คํํด์ ์ด๋ฒคํธ ๋ฆฌ์ค๋ ๋ฑ๋ก (Hydration)
์๋ก์ด ๋ฐฉ์ (Resumability)
1. ์๋ฒ์์ HTML + ์ํ ์ ๋ณด ์์ฑ
2. ํด๋ผ์ด์ธํธ๋ก ์ ์ก
3. ํ์ํ ๋ถ๋ถ๋ง ์ฆ์ ์ธํฐ๋ํฐ๋ธ (Hydration ๋ถํ์!)
4. ๋๋จธ์ง๋ ์ฌ์ฉ์๊ฐ ํด๋ฆญํ ๋ ๋ก๋
// Qwik ์์
import { component$, useSignal } from '@builder.io/qwik';
export const Counter = component$(() => {
const count = useSignal(0);
return (
<div>
<p>Count: {count.value}</p>
<button onClick$={() => count.value++}>
Increment
</button>
</div>
);
});
// onClick$์ $๋ "์ด ์ฝ๋๋ฅผ ๋์ค์ ๋ก๋ํด๋ ๋ผ"๋ผ๋ ์๋ฏธ!
ํ์ ์ ์ธ ์ :- Time to Interactive(TTI)๊ฐ ๊ฑฐ์ 0์ ๊ฐ๊น์์ง! โก
- ์ด๊ธฐ JavaScript ์คํ ์์
- ๋๊ท๋ชจ ์ฑ์์๋ ๋น ๋ฅธ ์์
4๏ธโฃ WebAssembly (WASM) ํตํฉ
๋ฌด๊ฑฐ์ด ๊ณ์ฐ์ WebAssembly๋ก!// Rust๋ก ์์ฑ๋ ๋ฌด๊ฑฐ์ด ๋ก์ง
// heavy_calculation.rs
#[wasm_bindgen]
pub fn process_large_data(data: &[u8]) -> Vec<u8> {
// ๋ณต์กํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
// JavaScript๋ณด๋ค 10๋ฐฐ ๋น ๋ฆ!
}
// JavaScript์์ ์ฌ์ฉ
import init, { process_large_data } from './heavy_calculation.wasm';
async function processData(data) {
await init();
const result = process_large_data(data);
return result;
}
์ฌ์ฉ ์ฌ๋ก:- ์ด๋ฏธ์ง/๋น๋์ค ์ฒ๋ฆฌ ๐จ
- ์ํธํ/๋ณตํธํ ๐
- ๊ฒ์ ์์ง ๐ฎ
- ๊ณผํ ๊ณ์ฐ ๐ฌ
๐ ์ค๋ฌด ์ฒดํฌ๋ฆฌ์คํธ: ์ฑ๋ฅ ์ต์ ํ ๊ฐ์ด๋
๐ฏ ์ด๊ธฐ ๋ก๋ฉ ์ต์ ํ
- โ ์ฝ๋ ์คํ๋ฆฌํ ์ ์ฉ (๋์ import)
- โ ์ด๋ฏธ์ง ์ต์ ํ (WebP, lazy loading)
- โ ํฐํธ ์ต์ ํ (font-display: swap)
- โ CSS ์ต์ํ ๋ฐ ์ธ๋ผ์ธ ํฌ๋ฆฌํฐ์ปฌ CSS
- โ JavaScript ๋ฒ๋ค ํฌ๊ธฐ ๋ถ์ (webpack-bundle-analyzer)
- โ Tree shaking ํ์ธ
- โ Gzip/Brotli ์์ถ ํ์ฑํ
- โ CDN ์ฌ์ฉ
- โ HTTP/2 ๋๋ HTTP/3 ์ฌ์ฉ
โก ๋ฐํ์ ์ฑ๋ฅ ์ต์ ํ
- โ ๋ถํ์ํ ๋ฆฌ๋ ๋๋ง ์ ๊ฑฐ (React.memo, useMemo)
- โ ๊ฐ์ ์คํฌ๋กค ๊ตฌํ (๊ธด ๋ฆฌ์คํธ)
- โ ๋๋ฐ์ด์ค/์ฐ๋กํ ์ ์ฉ (์ด๋ฒคํธ ํธ๋ค๋ฌ)
- โ requestAnimationFrame ์ฌ์ฉ (์ ๋๋ฉ์ด์ )
- โ Web Worker ํ์ฉ (๋ฌด๊ฑฐ์ด ๊ณ์ฐ)
- โ ์ด๋ฒคํธ ์์ ์ฌ์ฉ
- โ DOM ์กฐ์ ๋ฐฐ์น ์ฒ๋ฆฌ
- โ CSS ์ ๋๋ฉ์ด์ ์ฐ์ (transform, opacity)
- โ will-change ์์ฑ ์ ์ ํ ์ฌ์ฉ
๐ ๋ชจ๋ํฐ๋ง ๋ฐ ์ธก์
โ Core Web Vitals ํต๊ณผ
- LCP (Largest Contentful Paint) < 2.5s
- FID (First Input Delay) < 100ms
- CLS (Cumulative Layout Shift) < 0.1
โ Performance ํญ์ผ๋ก ํ๋กํ์ผ๋ง
โ React DevTools Profiler ํ์ธ
โ ์ค์ ์ฌ์ฉ์ ๋ชจ๋ํฐ๋ง (RUM) ์ค์
โ ์๋ฌ ์ถ์ ๋๊ตฌ ์ฐ๋ (Sentry ๋ฑ)
๐ฑ ๋ชจ๋ฐ์ผ ์ต์ ํ
- โ ํฐ์น ์ด๋ฒคํธ ์ต์ ํ
- โ ๋ทฐํฌํธ ์ค์ ํ์ธ
- โ ๋ชจ๋ฐ์ผ ๋คํธ์ํฌ ํ ์คํธ (3G ํ๊ฒฝ)
- โ ์คํ๋ผ์ธ ์ง์ (Service Worker)
- โ ๋ฐฐํฐ๋ฆฌ ์๋ชจ ์ต์ํ
- โ ๋ฐ์ํ ์ด๋ฏธ์ง ์ฌ์ฉ (srcset)
- โ ํฐ์น ํ๊ฒ ํฌ๊ธฐ ์ถฉ๋ถํ (์ต์ 44x44px)
๐ก๏ธ ๋ณด์ ๋ฐ ์ ๊ทผ์ฑ
- โ XSS ๋ฐฉ์ง (์ ๋ ฅ ๊ฒ์ฆ, ์ด์ค์ผ์ดํ)
- โ CSRF ํ ํฐ ์ฌ์ฉ
- โ HTTPS ์ฌ์ฉ
- โ Content Security Policy ์ค์
- โ ํค๋ณด๋ ๋ค๋น๊ฒ์ด์ ์ง์
- โ ์คํฌ๋ฆฐ ๋ฆฌ๋ ํธํ์ฑ
- โ ARIA ๋ ์ด๋ธ ์ ์ ํ ์ฌ์ฉ
- โ ์์ ๋๋น ์ถฉ๋ถํ (WCAG AA ์ด์)
๋จผ์ ์๋ํ๋ ์ฝ๋๋ฅผ ๋ง๋ค๊ณ , ํ๋กํ์ผ๋ง์ผ๋ก ๋ณ๋ชฉ์ ์ฐพ์ ๋ค์, ํ์ํ ๋ถ๋ถ๋ง ์ต์ ํํ์. ๋ชจ๋ ๊ฑธ ๋ค ์ต์ ํํ๋ ค๋ค๊ฐ ๊ฐ๋ฐ ์๊ฐ๋ง ๋์ด๋ ์ ์์ด! ๐ฏ
๐ฌ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ (FAQ)
Q1. ๊ฐ์ DOM์ด ํญ์ ๋น ๋ฅธ๊ฐ์?
๊ฐ์ DOM์ด ๋น ๋ฅธ ๊ฒฝ์ฐ:
- ๋ณต์กํ UI์์ ๋ถ๋ถ์ ์ธ ์ ๋ฐ์ดํธ๊ฐ ๋ง์ ๋
- ์ฌ๋ฌ ์ํ ๋ณ๊ฒฝ์ด ๋์์ ์ผ์ด๋ ๋
- ๊ฐ๋ฐ์๊ฐ ์ต์ ํ๋ฅผ ์ ๊ฒฝ ์ฐ์ง ์์๋ ๋ ๋
์์ DOM์ด ๋น ๋ฅธ ๊ฒฝ์ฐ:
- ๋จ์ํ UI
- ํ ๋ฒ๋ง ๋ ๋๋งํ๊ณ ๋
- ์ ํํ ์ด๋ค ๋ถ๋ถ์ ์ ๋ฐ์ดํธํ ์ง ์ ๋
์๋ฅผ ๋ค์ด, ๋จ์ํ ํ ์คํธ ํ๋ ๋ฐ๊พธ๋ ๊ฑฐ๋ผ๋ฉด element.textContent = 'new text'๊ฐ ๊ฐ์ DOM๋ณด๋ค ๋นจ๋ผ!
Q2. React vs Vue, ์ฑ๋ฅ ์ฐจ์ด๊ฐ ์๋์?
๋ฒค์น๋งํฌ ํ ์คํธ์์๋ Vue 3๊ฐ ์ฝ๊ฐ ๋น ๋ฅด๊ฒ ๋์ค๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ง๋ง, ์ค๋ฌด์์๋ ์ฝ๋ ํ์ง๊ณผ ์ต์ ํ ๊ธฐ๋ฒ์ด ํจ์ฌ ์ค์ํด.
๋ ๋ค ์ถฉ๋ถํ ๋น ๋ฅด๋๊น, ํ์ ์ ํธ๋๋ ์ํ๊ณ๋ฅผ ๊ธฐ์ค์ผ๋ก ์ ํํ๋ ๊ฒ ์ข์! ๐ฏ
Q3. key๋ฅผ index๋ก ์ฐ๋ฉด ์ ๋๋ ์ด์ ๊ฐ ๋ญ๊ฐ์?
// ์ด๊ธฐ ์ํ
[
{ id: 1, name: 'Alice' }, // index: 0
{ id: 2, name: 'Bob' }, // index: 1
{ id: 3, name: 'Charlie' } // index: 2
]
// Alice๋ฅผ ์ญ์ ํ๋ฉด?
[
{ id: 2, name: 'Bob' }, // index: 0 (์ด์ ์๋ 1)
{ id: 3, name: 'Charlie' } // index: 1 (์ด์ ์๋ 2)
]
index๋ฅผ key๋ก ์ฐ๋ฉด, React๋ "index 0์ ๋ด์ฉ์ด Alice์์ Bob์ผ๋ก ๋ฐ๋์๋ค?"๋ผ๊ณ ์๊ฐํด์ ๋ถํ์ํ ์
๋ฐ์ดํธ๋ฅผ ํด. ์ฌ์ง์ด input ๊ฐ์ด๋ ์ ๋๋ฉ์ด์
์ํ๊ฐ ๊ผฌ์ผ ์๋ ์์ด! ๐ฑํด๊ฒฐ์ฑ : ํญ์ ๊ณ ์ ํ ID๋ฅผ key๋ก ์ฌ์ฉํ์!
Q4. useMemo์ useCallback์ ์ธ์ ์จ์ผ ํ๋์?
useMemo๋ฅผ ์ธ ๋:
- ๊ณ์ฐ ๋น์ฉ์ด ๋์ ์์ (๋ณต์กํ ํํฐ๋ง, ์ ๋ ฌ ๋ฑ)
- ์ฐธ์กฐ ๋์ผ์ฑ์ด ์ค์ํ ๊ฒฝ์ฐ (์์กด์ฑ ๋ฐฐ์ด์ ๋ค์ด๊ฐ๋ ๊ฐ์ฒด/๋ฐฐ์ด)
useCallback์ ์ธ ๋:
- ์์ ์ปดํฌ๋ํธ์ props๋ก ์ ๋ฌ๋๋ ํจ์
- ๋ค๋ฅธ Hook์ ์์กด์ฑ ๋ฐฐ์ด์ ๋ค์ด๊ฐ๋ ํจ์
์ฐ์ง ๋ง์์ผ ํ ๋:
- ๋จ์ํ ๊ณ์ฐ (์ซ์ ๋ํ๊ธฐ ๋ฑ)
- ๋ ๋๋ง ๋น์ฉ์ด ๋ฎ์ ์ปดํฌ๋ํธ
- ํ๋กํ์ผ๋ง ์์ด "ํน์ ๋ชฐ๋ผ์"
๊ธฐ์ตํด: useMemo์ useCallback๋ ๋น์ฉ์ด ๋ค์ด! ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ , ์์กด์ฑ ๋น๊ต ๋ก์ง์ด ์คํ๋๊ฑฐ๋ .
Q5. ๋ฒ๋ค ํฌ๊ธฐ๋ฅผ ์ค์ด๋ ๊ฐ์ฅ ํจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ์?
1. ์ฌ์ฉํ์ง ์๋ ์ฝ๋ ์ ๊ฑฐ
- webpack-bundle-analyzer๋ก ๋ถ์
- ์ ์ฐ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๊ฑฐ
- Tree shaking ํ์ธ
2. ์ฝ๋ ์คํ๋ฆฌํ
// ๋์ import ์ฌ์ฉ
const HeavyComponent = lazy(() => import('./HeavyComponent'));3. ๊ฐ๋ฒผ์ด ๋์ ์ฌ์ฉ
- moment.js (288KB) โ date-fns (13KB)
- lodash (์ ์ฒด) โ lodash-es (ํ์ํ ๊ฒ๋ง)
- axios โ fetch API
4. ์์ถ ๋ฐ ์ต์ ํ
- Terser๋ก minify
- Gzip/Brotli ์์ถ
- ์ด๋ฏธ์ง ์ต์ ํ
Q6. ์ฌ๋ฅ๋ท์์ ํ๋ฆฌ๋์๋ก ์ผํ ๋ ์ฑ๋ฅ ์ต์ ํ ์๊ตฌ์ฌํญ์ด ์๋์?
- Lighthouse ์ ์ 90์ ์ด์
- ๋ชจ๋ฐ์ผ์์ 3์ด ์ด๋ด ๋ก๋ฉ
- ๋ถ๋๋ฌ์ด ์คํฌ๋กค ๋ฐ ์ ๋๋ฉ์ด์
- ํฌ๋ก์ค ๋ธ๋ผ์ฐ์ ํธํ์ฑ
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ๋ก์ ํธ๋ฅผ ๋ฐ์ ๋๋ ์ฑ๋ฅ ๊ธฐ์ค์ ๋ช ํํ ํ๊ณ ๊ณ์ฝ์์ ํฌํจ์ํค๋ ๊ฒ ์ข์. ๋์ค์ ๋ถ์์ ํผํ ์ ์๊ฑฐ๋ ! ๐
๐ ํ์ต ๋ฆฌ์์ค ๋ฐ ๋ค์ ๋จ๊ณ
๐ ํ๋ ๋ฌธ์
- Vue ๊ณต์ ๋ฌธ์: vuejs.org
- MDN Web Docs: developer.mozilla.org
์ฑ๋ฅ ๊ด๋ จ
- web.dev (Google์ ์น ์ฑ๋ฅ ๊ฐ์ด๋)
- JavaScript.info (๊น์ด ์๋ JavaScript ํ์ต)
- React Performance ๊ฐ์ด๋
- Vue Performance ๊ฐ์ด๋
๐ ๏ธ ์ ์ฉํ ๋๊ตฌ๋ค
| ๋๊ตฌ | ์ฉ๋ | ์ถ์ฒ๋ |
|---|---|---|
| Chrome DevTools | ์ฑ๋ฅ ํ๋กํ์ผ๋ง, ๋๋ฒ๊น | โญโญโญโญโญ |
| React DevTools | React ์ปดํฌ๋ํธ ๋ถ์ | โญโญโญโญโญ |
| Lighthouse | ์ ์ฒด ์ฑ๋ฅ ์ ์ | โญโญโญโญโญ |
| webpack-bundle-analyzer | ๋ฒ๋ค ํฌ๊ธฐ ๋ถ์ | โญโญโญโญโญ |
| React Query | ๋ฐ์ดํฐ ํ์นญ ์ต์ ํ | โญโญโญโญ |
| SWR | ๋ฐ์ดํฐ ์บ์ฑ | โญโญโญโญ |
| Vite | ๋น ๋ฅธ ๊ฐ๋ฐ ์๋ฒ | โญโญโญโญโญ |
๐ฏ ์ค์ต ํ๋ก์ ํธ ์์ด๋์ด
์ด๋ก ๋ง ๋ณด์ง ๋ง๊ณ ์ง์ ๋ง๋ค์ด๋ณด์! ์ด๋ฐ ํ๋ก์ ํธ๋ค์ ์ถ์ฒํด:- ์ด๋ฏธ์ง lazy loading
- ์คํฌ๋กค ์ด๋ฒคํธ ์ต์ ํ
2. ์ค์๊ฐ ๋์๋ณด๋
- WebSocket ์ฐ๋
- ์ฐจํธ ๋ ๋๋ง ์ต์ ํ
- ๋ฐ์ดํฐ ๋ฐฐ์น ์ ๋ฐ์ดํธ
3. ๋์ฉ๋ ๋ฐ์ดํฐ ํ ์ด๋ธ
- ๊ฐ์ ์คํฌ๋กค
- ์ ๋ ฌ/ํํฐ๋ง ์ต์ ํ
- ํ์ด์ง๋ค์ด์
4. ์ด๋ฏธ์ง ๊ฐค๋ฌ๋ฆฌ
- ์ด๋ฏธ์ง ์ต์ ํ
- Intersection Observer
- ํ๋ฆฌ๋ก๋ฉ ์ ๋ต
๐ก ๋ค์ ๋จ๊ณ
์ด ๊ธ์ ๋ค ์ฝ์๋ค๋ฉด, ์ด์ ์ด๋ฐ ๊ฑธ ํด๋ณด์:- DOM ์กฐ์ ์ต์ ํ ๊ธฐ๋ฒ ์ ์ฉ
- Performance ํญ์ผ๋ก ์ธก์
2์ฃผ์ฐจ: ํ๋ ์์ํฌ ์ตํ๊ธฐ
- React ๋๋ Vue๋ก ๊ฐ์ ์ฑ ๋ง๋ค๊ธฐ
- ๊ฐ์ DOM์ ๋์ ์ดํด
- ์ฑ๋ฅ ๋น๊ต
3์ฃผ์ฐจ: ๊ณ ๊ธ ์ต์ ํ
- ์ฝ๋ ์คํ๋ฆฌํ ์ ์ฉ
- ๋ฉ๋ชจ์ด์ ์ด์ ๊ธฐ๋ฒ
- ๋ฒ๋ค ํฌ๊ธฐ ์ต์ ํ
4์ฃผ์ฐจ: ์ค์ ํ๋ก์ ํธ
- ๋ณต์กํ ์ฑ ๋ง๋ค๊ธฐ
- ๋ชจ๋ ์ต์ ํ ๊ธฐ๋ฒ ์ ์ฉ
- Lighthouse 90์ ์ด์ ๋ฌ์ฑ!
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
DOM ์กฐ์๊ณผ ๊ฐ์ DOM์ ์ฑ๋ฅ ์ฐจ์ด์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ค๋ค์ด. ํต์ฌ์ ๋ค์ ํ๋ฒ ์ ๋ฆฌํ๋ฉด:
โ ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ ๋ถ๋ฆฌํ๊ณ , ๋ฐฐ์น ์ฒ๋ฆฌํ์!
2. ๊ฐ์ DOM์ ๋ง๋ฅ์ด ์๋๋ค. ์ํฉ์ ๋ง๊ฒ ์ ํํ์.
โ ๋ณต์กํ UI์๋ ๊ฐ์ DOM, ๋จ์ํ ์์ ์ ์์ DOM
3. ์ต์ ํ๋ ์ธก์ ํ์ ํ์.
โ ํ๋กํ์ผ๋ง ๋จผ์ , ์ต์ ํ๋ ๋์ค์
4. ํ๋ ์์ํฌ ์ ํ์ ๊ธฐ์ ์ ์์๋ง ๊ณ ๋ คํ์ง ๋ง์.
โ ํ, ์ํ๊ณ, ์ ์ง๋ณด์์ฑ๋ ์ค์ํด
5. ์ฌ์ฉ์ ๊ฒฝํ์ด ์ต์ฐ์ ์ด๋ค.
โ ์ซ์๋ณด๋ค ์ค์ ์ฒด๊ฐ ์ฑ๋ฅ์ด ์ค์ํด
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ๋ฆฌ๋์๋ก ์ผํ๋ , ํ์ฌ์์ ์ ๊ท์ง์ผ๋ก ์ผํ๋ , ์ฑ๋ฅ ์ต์ ํ ๋ฅ๋ ฅ์ ์ ๋ง ์ค์ํ ์คํฌ์ด์ผ. ํด๋ผ์ด์ธํธ๋ ์ฌ์ฉ์๋ค์ด "์ด ์ฌ์ดํธ ๋น ๋ฅด๋ค!"๋ผ๊ณ ๋งํ ๋์ ๊ทธ ๋ฟ๋ฏํจ์ด๋! ๐
๋งํ๋ ๋ถ๋ถ์ด ์์ผ๋ฉด ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ๊ณ , ๋ค๋ฅธ ์ฌ๋์ ์ฝ๋๋ ์ฝ์ด๋ณด๊ณ , ๊ณ์ ํ์ตํ์. ์ฐ๋ฆฌ ๋ชจ๋ ์ฑ์ฅํ๋ ๊ฐ๋ฐ์๊ฐ ๋์! ๐ฑ
Happy coding! ๐ปโจ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.