안녕하세요 초원입니다. 👩🏻💻
최근 자바스크립트로 직접 DOM을 구현하는 과제 중,
부모 요소에 appendChild를 호출하여 자식을 추가할 때마다
브라우저가 매번 렌더링을 수행하는 문제를 겪었습니다.
이 글에서는 DocumentFragment를 사용하여 이를 해결하는 과정을 소개드립니다.
> 자바스크립트 엔진과 렌더링 엔진
'리액트에서 state, props가 렌더링을 유발한다'는 개념만 달달 외우다 보니
자바스크립트에서는 언제 렌더링이 되는 것인지 당황스럽게도 기억이 안났습니다.
그래서 다시 한 번 정리해봅니다.
• 자바스크립트 엔진: DOM 조작을 수행하고, 변경 사항을 렌더링 엔진에 전달합니다.
• 렌더링 엔진: DOM을 받아 레이아웃 재계산 및 화면 그리기를 수행합니다. (비용이 큼)
> 렌더링 엔진은 언제 리렌더링을 수행할까?
1. DOM 조작
• innerHTML, outerHTML, textContent 등의 속성을 사용하여 DOM 요소의 콘텐츠를 변경할 때
• appendChild, removeChild, replaceChild 등을 사용하여 DOM 구조를 변경할 때
• setAttribute, removeAttribute 등을 사용하여 속성을 변경할 때
2. 스타일 변경
• 요소의 style 속성을 변경할 때 (element.style.color = 'red' 등)
• CSS 클래스를 추가하거나 제거할 때 (element.classList.add('className'), element.classList.remove('className') 등)
• CSSOM을 통해 동적으로 스타일 시트를 변경할 때 (document.styleSheets[0].cssRules)
3. 레이아웃 (reflow) 변경
• 요소의 위치나 크기를 변경할 때 (element.style.width = '100px', element.style.height = '100px' 등)
• 브라우저 창의 크기를 조절할 때
• 폰트 크기를 변경할 때
4. 페인트 (repaint) 변경
• 요소의 배경색, 글자색, 그림자 등의 스타일을 변경할 때
• 요소의 가시성(visibility, display)을 변경할 때
5. 애니메이션
• CSS 애니메이션 또는 전환(CSS transitions, animations)을 시작할 때
• JavaScript를 사용한 애니메이션 (예: requestAnimationFrame, setInterval 등)
6. 폼 요소 업데이트
• 입력 필드, 체크박스 등의 값을 변경할 때
• 사용자가 폼 요소와 상호작용할 때
7. 이벤트
• 사용자 상호작용 (클릭, 키 입력 등)에 반응하여 DOM을 변경할 때
• 타이머 이벤트 (setTimeout, setInterval)가 실행될 때
8. 네트워크 요청
• Ajax 요청이 완료되고 DOM을 업데이트할 때
• 서버로부터 데이터를 받아서 페이지 내용을 변경할 때
이러한 동작들이 발생하면 브라우저는
필요한 경우 레이아웃을 다시 계산하고, 페인팅 과정을 통해 화면을 갱신합니다.
이는 웹 페이지의 성능에 영향을 미칠 수 있으므로, 효율적인 렌더링을 위해
변경 사항을 최소화하거나, 한 번에 묶어서 적용하는 등의 최적화가 필요합니다.
> 잦은 렌더링이 발생하는 자바스크립트 예제
제가 겪은 문제를 예제를 통해 확인해보겠습니다.
const list = document.getElementById('list');
for (let i = 0; i < 1000; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
list.appendChild(item); // 각 반복마다 렌더링 발생
}
이것은 각 반복마다 렌더링을 총 1000번 발생시키는 문제가 있습니다.
이를 Documentfragment로 최적화 할 수 있습니다.
아래는 실제 DOM을 구현하기 위해 작성한 코드 중 문제가 발생한 부분입니다.
> DocumentFragment란 무엇인가?
DocumentFragment는 자바스크립트에서 DOM 조작을 최적화하기 위해 제공되는 특별한 노드입니다.
일반적인 DOM 조작은 여러 번의 렌더링을 유발할 수 있어 성능 저하를 일으킬 수 있습니다.
하지만 DocumentFragment를 사용하면, 메모리에 존재하는 가상의 DOM에 여러 요소를 추가한 뒤,
한 번에 실제 DOM에 삽입하기 때문에 단 한 번의 리렌더링만 수행됩니다.
// DocumentFragment 생성
const fragment = document.createDocumentFragment();
// 여러 요소를 Fragment에 추가
for (let i = 0; i < 1000; i++) {
const newElement = document.createElement('div');
newElement.textContent = `Item ${i}`;
fragment.appendChild(newElement);
}
// Fragment를 한 번에 DOM에 추가
document.body.appendChild(fragment);
위 예제에서는 1000개의 div 요소를 한 번에 DocumentFragment에 추가한 뒤,
최종적으로 한 번의 appendChild 호출로 DOM에 추가합니다.
이를 통해 브라우저의 렌더링 성능을 크게 향상시킬 수 있습니다.
> 꼭 fragment를 써야할까?
fragment를 쓰지 않고 렌더링을 최적화 하는 방법이 있습니다. 코드 순서만 바꾸면 되는데요.
요소를 먼저 메모리에 생성하고, 마지막에 한 번에 추가하는 방식으로 동일한 효과를 얻을 수 있습니다.
// 요소들을 담을 부모 요소 선택
const list = document.getElementById('list');
// 메모리에 새로운 요소들을 생성하여 배열에 저장
const items = [];
for (let i = 0; i < 1000; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
items.push(item); // 메모리에 저장
}
// 생성된 요소들을 한 번에 DOM에 추가
list.append(...items);
* 사실은 WEB API를 알고 잘 사용하는게 아니라 본질을 알면 개선이 가능하다는 점을 잘 전달하면 좋을 듯
* 그것을 위해 렌더링, DOM, 메모리 개념을 추후 언급할 것이라는 것도 기재하면 좋을 듯
> 여기서 잠깐, Element를 생성하면 무조건 렌더링이 되나요?
제가 헷갈려서 잠시 짚고 넘어갑니다.
예를 들어 createElement를 하면 렌더링이 되나요? 그냥 메모리에 올라가는 것 뿐입니다.
다만 이미 렌더링 되어 DOM트리에 올라간 부모요소에게 createElement를 해서 자식을 추가한다면?
그것은 추가할 때마다 렌더링을 일으키는 원인이 됩니다.
좀 더 자세히 알아보겠습니다.
> DOM과 메모리
1. DOM의 저장 위치
• DOM은 브라우저의 메모리에 저장됩니다. 브라우저는 HTML 문서를 파싱하여 DOM 트리를 생성하고, 이 트리를 메모리에 유지합니다. 이를 통해 자바스크립트는 DOM 트리를 조작하여 웹 페이지의 내용을 동적으로 변경할 수 있습니다.
2. DocumentFragment의 저장 위치
• DocumentFragment 역시 브라우저의 메모리에 저장되지만, 이는 DOM 트리와는 독립적으로 존재합니다. DocumentFragment는 부모 노드가 없기 때문에 실제 DOM 트리의 일부가 아닙니다. 대신, 이는 메모리 상의 가상 노드로 존재하며, DOM 트리에 추가되기 전까지는 렌더링에 영향을 미치지 않습니다.
> 메모리 구조
브라우저의 메모리 구조를 간단히 설명하면 다음과 같습니다.
• 힙(Heap) : 자바스크립트 객체와 같은 동적으로 할당되는 메모리 블록을 저장하는 공간입니다. DOM과 DocumentFragment 객체 역시 힙에 저장됩니다.
• 스택(Stack): 함수 호출과 관련된 메모리를 저장하는 공간입니다. 함수의 실행 컨텍스트와 지역 변수 등이 여기에 저장됩니다.
> 리액트의 Batching이란 무엇인가?
저는 새롭게 알게 된 개념을 기존에 알던 지식 중 비슷한 개념이 있는지 비교하는 작업을 좋아합니다.
어? 배칭도 잦은 렌더링을 방지하기 위한 장치였던 것 같은데?
과연 이 두 가지 개념을 비슷하다고 볼 수 있을까?
Batching
• 여러 상태 변화를 하나의 렌더링 업데이트로 묶어 처리하는 기술입니다.
• 이를 통해 불필요한 렌더링을 줄이고 성능을 최적화합니다.
근데 너무 길어져서 다음 게시글로 찾아오겠습니다 .. 열심히 공부해서 다음 주에 돌아올게요!