가상 DOM
고친 사람 github-actions[bot]
가상 DOM 은 화면을 고칠 때 바뀐 곳만 골라 고치게 해 주는 방법입니다. 화면이 어떻게 생겨야 하는지를 먼저 메모리에 그려 둡니다. 새로 그린 것을 앞서 그린 것과 견줍니다. 그리고 달라진 곳만 브라우저 화면에 옮깁니다.
쉽고 빠른 이해
가상 DOM 은 화면의 도면을 메모리에 그려 두고 바뀐 곳만 화면에 고치는 방법입니다. 장바구니 목록에 상품이 백 줄 있고 한 줄의 수량만 3에서 4로 바뀌었다고 합시다. 그러면 화면에서는 그 숫자 하나만 바뀝니다.
화면을 직접 고치는 코드는 무엇을 어떤 순서로 바꿀지 개발자가 하나하나 적어야 합니다. 화면이 복잡해질수록 빠뜨리기 쉽습니다. 그렇다고 바뀔 때마다 화면을 다 지우고 새로 그리면 브라우저가 할 일이 늘어납니다. 입력하던 글자와 스크롤 위치도 날아갑니다.
- 데이터가 바뀌면 화면 전체의 도면을 객체로 새로 그립니다
- 앞서 그린 도면과 견줘 달라진 곳을 찾습니다
- 그곳만 브라우저 화면에 옮깁니다
대가는 덧붙는 일입니다. 바뀔 때마다 도면을 새로 그리고 견줘야 합니다. 어디를 고칠지 이미 아는 코드라면 이 일 없이 바로 고칩니다.
상세
집을 고칠 때 설계사는 바뀐 모습대로 새 도면을 그립니다. 도면은 종이라 몇 번을 다시 그려도 부담이 적습니다.
현장 소장은 새 도면을 옛 도면 위에 겹쳐 봅니다. 달라진 벽 하나에만 표시를 해서 공사팀에 넘깁니다. 공사팀은 집을 허물지 않고 그 벽 하나만 고칩니다.
비유의 집은 DOM(Document Object Model, 문서 객체 모델)입니다. 브라우저가 들고 있는 실제 화면입니다. 설계사는 화면의 모습을 적는 개발자의 코드입니다. 현장 소장과 공사팀은 화면을 고치는 라이브러리입니다.
가상 DOM 이라는 이름은 두 가지를 가리킵니다. 도면 자체를 가리킬 때가 있습니다. 도면을 겹쳐 보고 달라진 곳만 고치는 방법 전체를 가리킬 때도 있습니다.
이 글에서 「가상 DOM」은 방법을 뜻합니다. 도면은 「가상 DOM 트리」라고 적습니다. 트리가 무엇인지는 바로 아래 소절에서 풉니다.
실제 화면을 담는 DOM
웹 문서는 <ul> · <li> 같은 태그로 조각나 있습니다. 태그 하나로 적힌 조각을 요소라고 부릅니다.
브라우저는 문서를 읽어 요소를 부모와 자식으로 잇습니다. <ul> 안에 <li> 가 있으면 <li> 는
<ul> 의 자식입니다.
이렇게 가지를 치며 이어진 구조를 트리라고 합니다. 트리를 이루는 칸 하나하나는 노드입니다. 브라우저가 웹 문서로 만든 이 트리가 DOM 입니다.
화면에 보이는 것은 DOM 을 따라 그려집니다. 자바스크립트 코드는 DOM 의 노드를 더하고 지우고 고쳐서 화면을 바꿉니다. 목록에 줄을 하나 더하려면 노드를 하나 만들어 목록 노드 밑에 붙입니다.
DOM 을 고치면 브라우저가 뒤따르는 일을 합니다. 노드의 크기나 위치가 바뀌면 둘레 요소의 크기와 위치를 다시 계산합니다. 이 계산이 레이아웃입니다.
계산이 끝나면 브라우저는 화면에 다시 칠합니다. 색과 글자와 테두리를 화면의 점으로 옮기는 일입니다. 이것이 페인트입니다.
그래서 DOM 을 고치는 횟수와 범위가 곧 브라우저의 일감입니다. 바뀐 글자 하나를 고치면 그 둘레만 다시 계산합니다. 목록 전체를 지우고 새로 붙이면 목록 전체를 다시 계산합니다.
명령형으로 고치는 코드가 번거로운 까닭
DOM 을 직접 고치는 코드는 순서를 적는 코드입니다. 「이 노드의 글자를 바꿔라」, 「저 노드를 지워라」를 한 줄씩 적습니다. 무엇을 할지를 차례로 지시하는 이런 방식을 명령형이라고 합니다.
화면이 단순하면 명령형으로도 충분합니다. 그런데 한 데이터가 화면 여러 곳에 걸쳐 있으면 사정이 달라집니다. 장바구니에 상품을 하나 담으면 목록 줄, 합계 금액, 상단의 개수 표시가 함께 바뀌어야 합니다. 셋 중 하나를 빠뜨리면 화면과 데이터가 서로 다른 말을 합니다.
반대쪽 방법은 「화면이 이렇게 생겨야 한다」만 적는 것입니다. 데이터를 받아 화면의 모습을 돌려주는 함수를 하나 씁니다. 데이터가 바뀌면 그 함수를 다시 부르면 됩니다. 결과만 적고 과정은 맡기는 이런 방식을 선언형이라고 합니다.
선언형 함수의 결과를 DOM 에 바로 옮기면 문제가 생깁니다. 함수는 매번 화면 전체의 모습을 돌려줍니다. 그대로 옮기면 DOM 을 다 지우고 새로 붙이게 됩니다. 브라우저의 일감이 화면 전체로 커집니다. 입력칸에 치던 글자와 스크롤 위치도 노드와 함께 사라집니다.
가상 DOM 은 이 둘 사이를 잇습니다. 개발자는 선언형으로 화면 전체를 그리는 코드를 씁니다. 라이브러리는 그 결과를 앞의 결과와 견줘 DOM 에 줄 명령을 최소한으로 골라냅니다.
가상 DOM 트리를 이루는 가상 노드
가상 DOM 트리는 평범한 자바스크립트 객체로 이루어집니다. 객체 하나가 화면의 요소 하나를 나타냅니다. 이 객체를 가상 노드라고 부릅니다.
아래는 목록 한 줄을 나타내는 가상 노드입니다. 요소의 종류, 속성, 자식을 담는 칸이 있습니다.
const vnode = {
type: 'li',
props: { class: 'item' },
children: ['사과 3개'],
};
type 은 어떤 요소인지를 적습니다. props 는 요소에 붙는 속성입니다. children 은 그 밑에
달리는 자식입니다. 자식 칸에 다른 가상 노드를 넣으면 트리가 됩니다.
이 객체는 브라우저 화면과 아무 관계가 없습니다. 만들어도 레이아웃과 페인트가 돌지 않습니다. 그래서 화면 전체를 나타내는 트리를 데이터가 바뀔 때마다 새로 만들어도 부담이 DOM 을 고칠 때보다 작습니다.
데이터를 받아 가상 노드 트리를 돌려주는 함수를 렌더 함수라고 부릅니다. 앞 소절에서 말한 선언형 함수가 이것입니다. 개발자가 쓰는 코드는 이 함수입니다. 가상 DOM 트리를 견주고 화면을 고치는 일은 라이브러리가 맡습니다.
화면이 크면 렌더 함수를 화면 조각마다 나눠 씁니다. 목록 한 줄, 합계 칸처럼 나눕니다. 렌더 함수 하나가 맡은 화면 조각을 컴포넌트라고 부릅니다.
두 트리를 견줘 고치는 흐름
화면이 처음 뜰 때는 견줄 것이 없습니다. 라이브러리는 렌더 함수가 돌려준 가상 DOM 트리를 뿌리부터 훑으며 노드마다 실제 DOM 노드를 만들어 붙입니다. 이 단계를 마운트(mount)라고 부릅니다.
그 뒤로 데이터가 바뀌면 흐름이 달라집니다. 렌더 함수를 다시 불러 새 가상 DOM 트리를 받습니다. 새 트리를 앞 트리와 노드 단위로 견줍니다. 달라진 노드에 대해서만 DOM 에 명령을 보냅니다.
flowchart TD
S["데이터가 바뀜"] --> R["렌더 함수를 다시 부름"]
subgraph M["자바스크립트 메모리"]
R --> N["새 가상 DOM 트리"]
O["앞 가상 DOM 트리"] --> C["두 트리 견주기"]
N --> C
end
C --> P["달라진 노드만 고침"]
subgraph B["브라우저"]
P --> D["실제 DOM"]
D --> L["레이아웃 · 페인트"]
end
위쪽 상자 안의 일은 객체를 만들고 비교하는 일이라 화면에 닿지 않습니다. 화면에 닿는 것은 견주기가 골라낸 노드뿐입니다. 고치기가 끝나면 새 트리를 보관해 둡니다. 다음에 데이터가 바뀌면 이것이 앞 트리가 됩니다.
장바구니 목록 하나로 견주기의 결과를 봅니다. 앞 트리에는 「사과 3개」와 「배 2개」 두 줄이 있었습니다. 사과 수량이 4로 바뀌고 「귤 1개」가 새로 담겼습니다. 표는 노드마다 견준 결과와 DOM 에 보내는 명령입니다.
| 노드 | 앞 트리 | 새 트리 | DOM 에 보내는 명령 |
|---|---|---|---|
목록 ul |
있음 | 있음 | 없음 |
| 첫째 줄 | 사과 3개 | 사과 4개 | 글자만 바꿈 |
| 둘째 줄 | 배 2개 | 배 2개 | 없음 |
| 셋째 줄 | 없음 | 귤 1개 | 새로 만들어 붙임 |
렌더 함수는 목록 전체를 다시 그렸습니다. 그래도 DOM 에 간 명령은 둘뿐입니다. 글자 하나를 바꾸고 줄 하나를 붙였습니다.
이렇게 두 트리를 견줘 바꿀 곳을 찾는 일을 재조정(reconciliation)이라고 부릅니다. 디핑(diffing) 이나 패치(patch)라고 부르는 라이브러리도 있습니다. 가리키는 일은 같습니다.
빨리 견주려고 쓰는 지름길
두 트리를 빠짐없이 견주려면 노드 하나하나를 상대 트리의 모든 노드와 맞대 봐야 합니다. 노드가 많아지면 이 일이 급격히 무거워집니다. 그래서 라이브러리는 견주는 범위를 좁힙니다.
먼저 같은 위치에 있는 노드끼리만 견줍니다. 두 노드의 종류가 다르면 더 들여다보지 않습니다. 그
노드와 밑에 달린 자식을 모두 새것으로 바꿉니다. div 가 span 으로 바뀌었다면 속이 비슷해도 새로
만든다는 뜻입니다.
위치로만 견주면 목록에서 탈이 납니다. 목록 맨 앞에 줄 하나를 끼우면 뒤의 줄이 전부 한 칸씩 밀립니다. 위치로 보면 모든 줄의 내용이 바뀐 것처럼 보여 전부 고치게 됩니다.
이를 막으려고 목록의 줄마다 키라는 이름표를 붙입니다. 키는 형제 사이에서 겹치지 않는 값입니다. 상품 번호처럼 데이터가 원래 가진 식별자를 씁니다. 라이브러리는 위치 대신 키로 짝을 맞춥니다. 그러면 새로 끼운 줄 하나만 만들어 붙입니다. 나머지 줄은 짝이 맞아 DOM 에 명령이 가지 않습니다.
가상 DOM 이 더하는 일
가상 DOM 은 DOM 을 고치는 일을 줄이는 대신 다른 일을 더합니다. 데이터가 바뀔 때마다 트리를 새로 만들고 견줘야 합니다. 이 일은 자바스크립트가 메모리에서 합니다.
그래서 가상 DOM 을 거친 갱신이 DOM 을 직접 고치는 코드보다 언제나 일이 적은 것은 아닙니다. 어느 노드를 고칠지 이미 아는 코드는 견주기 없이 그 노드만 고칩니다. 가상 DOM 이 줄여 주는 것은 「화면을 다 지우고 새로 그리는 방법」과 견준 일감입니다.
메모리도 더 씁니다. 견주려면 앞 트리와 새 트리를 한동안 함께 들고 있어야 합니다. 한 번도 안 바뀌는 부분도 다시 그릴 때마다 가상 노드가 새로 만들어집니다.
라이브러리는 이 일을 덜어 내는 장치를 둡니다. 입력이 앞과 같은 컴포넌트는 렌더 함수를 다시 부르지 않고 앞 결과를 씁니다. 이것이 메모이제이션입니다. 절대 안 바뀌는 부분을 미리 표시해 두고 견주기에서 빼는 방법도 있습니다.
가상 DOM 을 쓰는 라이브러리와 쓰지 않는 방식
React 는 가상 DOM 을 두고 화면을 고칩니다. 개발자가 쓴 컴포넌트가 돌려준 결과를 가상 DOM 트리로 받아 앞 트리와 견줍니다. 같은 방식으로 화면을 고치는 라이브러리가 여럿 있습니다.
가상 DOM 없이 선언형을 지키는 방식도 있습니다. 어느 데이터가 어느 DOM 노드에 쓰이는지를 미리 추적해 둡니다. 데이터가 바뀌면 트리를 견주지 않고 그 데이터에 묶인 노드만 바로 고칩니다. 이런 방식을 세밀한 반응성이라고 부릅니다.
두 방식은 일을 치르는 때가 다릅니다. 가상 DOM 은 바뀔 때마다 견주는 일을 치릅니다. 세밀한 반응성은 처음에 데이터와 노드의 연결을 추적하는 일을 치릅니다. 그 대신 바뀔 때는 견주지 않습니다.
가상 DOM 과 헷갈리는 이름
이름에 DOM 이나 트리가 들어가서 가상 DOM 과 헷갈리는 것이 둘 있습니다. 하나는 브라우저가 그리기 전에 만드는 트리입니다. 다른 하나는 브라우저가 요소 안에 따로 두는 DOM 입니다.
렌더 트리는 브라우저 안에 있습니다. 화면에 그릴 노드만 골라 스타일 값을 붙인 트리입니다. 가상 DOM 트리는 라이브러리가 자바스크립트 메모리에 둔 객체입니다. 스타일 값도 크기도 갖지 않습니다.
섀도 DOM은 브라우저 기능입니다. 요소 하나 안에 바깥과 떨어진 DOM 트리를 따로 붙여 스타일과 구조를 가둡니다. 섀도 DOM 은 실제 DOM 의 일부입니다. 화면에도 그려집니다. 가상 DOM 과는 이름만 닮았습니다.
관련 항목
가상 DOM 이 본뜨는 브라우저 구조
DOM · 노드 · 트리 · 브라우저 · JavaScript · HTML 요소
가상 DOM 을 이루는 구성 요소
가상 노드 · 렌더 함수 · 컴포넌트 · props
가상 DOM 이 거치는 처리 단계
재조정 · 디핑 · 렌더와 커밋
가상 DOM 이 줄이려는 브라우저 작업
레이아웃 · 리플로 · 페인트 · 리페인트 · 레이아웃 스래싱
가상 DOM 을 떠받치는 프로그래밍 방식
선언형 · 명령형 · 단방향 데이터 흐름 · 상태 관리
가상 DOM 을 채택한 화면 라이브러리
React · Vue.js · Preact · Inferno
가상 DOM 을 대신하는 화면 갱신 방식
세밀한 반응성 · 증분 DOM · Svelte · SolidJS
가상 DOM 의 비용을 줄이는 최적화 기법
메모이제이션 · memo · useMemo · React 컴파일러
가상 DOM 을 쓰는 렌더링 방식
클라이언트 사이드 렌더링 · 서버 사이드 렌더링 · 하이드레이션 · 단일 페이지 애플리케이션
가상 DOM 과 이름이 겹치는 개념
다른 이름: virtual DOM · VDOM · 버추얼 DOM