cadenzah's hideOut

~ 정처없이 떠돌 수 없어 속이 베베 꼬인 영혼을 위로하며 ~



가상 DOM과 DOM의 차이점

이 글은 The difference between Virtual DOM and DOM를 번역한 것입이다. 의역이 있을 수 있습니다

React는 메인 페이지에서부터 바로 가상 DOM으로 우리를 공격한다! 이 기능은 정말 중요한가보다!

그렇지만, 가상 DOM이란 대체 무엇인가?

DOM

정의는 확실히 해두자. DOM은 문서 객체 모델의 약자이고, 구조화된 텍스트에 대한 추상화이다. 웹 개발자들에게 이 텍스트란 곧 HTML 코드이고, DOM은 간단하게 HTML DOM으로 불린다. HTML의 요소(Element)는 DOM에서 노드(Node)로 불린다.

그렇다면, HTML이 텍스트인 반면, DOM은 이 텍스트에 대한 메모리 상의 표현이다.

프로그램 상의 인스턴스로 존재하는 프로세스와 비교해보자. 동일 프로그램에 대하여 여러 프로세스를 가질 수 있다. 이는 하나의 HTML에 대하여 여러 DOM을 가질 수 있는 것과 같다. (여러 탭에 걸쳐서 동일 페이지를 로드하는 것을 떠올려보자)

HTML DOM은 노드들을 순회하고 수정할 수 있는 인터페이스(API)를 제공한다. 여기에는 getElementById 또는 removeChild 등과 같은 메서드들이 포함된다. 보통 우리는 DOM을 다루는 데에 자바스크립트를 사용하는데, 그 이유는... 아무도 모른다 :).

그러니까, 우리가 웹 페이지의 컨텐츠를 동적으로 바꾸고 싶을 때마다, 우리는 DOM을 수정한다.

var item = document.getElementById("MyLI");
item.parentNode.removeChild(item);

document는 루트 노드에 대한 추상화이고, getElementById, parentNode, removeChild는 HTML DOM API의 메서드 중 하나이다.

문제

HTML DOM은 항상 트리 구조를 띤다. 이것은 HTML 문서의 구조로 인하여 가능하다. 이로 인하여 트리를 상당히 쉽게 순회할 수 있으니 아주 좋은 것이다. 그렇지만 쉽다는 것이 빠르다는 건 아니다.

오늘날의 DOM 트리는 거대하다. 동적인 웹 어플리케이션(단일 페이지 어플리케이션)을 향하여 나아가는 추세에 따라, DOM 트리를 끊임없이, 그리고 많이 수정해야 한다. 또한 이것은 성능과 개발에 있어 고통 그 자체이다.

여담인데, 나는 5GB가 넘는 소스를 사용한 웹 페이지를 만든 적이 있다. 그렇게 어렵지는 않았다.

수천 개의 div로 이루어진 DOM을 생각해보자. 지금 우리는 최신 웹 개발자이고, 우리의 어플리케이션은 SPA라는 점을 기억하자. 우리는 이벤트를 다루는 여러 메서드들(click, submit, input 등)을 사용해야 한다. 전형적인 jQuery스러운 이벤트 핸들러는 아래와 같은 방식을 보일 것이다.
  • 해당 이벤트에 관심이 있는 노드를 모두 탐색한다.
  • 필요에 따라 해당 노드를 갱신한다.

여기에는 2가지 문제가 있다.

  1. 관리하기 힘들다. 이벤트 핸들러를 수정해야 한다고 가정해보자. 실행 흐름을 잊어버렸다면, 코드를 아주 유심히 들여다보며 동작 흐름을 파악해내야 한다. 시간도 많이 걸리고, 버그가 생기기도 쉽다.
  2. 비효율적이다. 탐색 작업을 일일이 수동으로 해야만 하는가? 어떤 노드가 갱신되어야 하는지 미리 알 수 있다면 좋지 않을까?

다시 말하지만, React는 우리에게 도움의 손길을 건넨다. 첫번째 문제에 대한 해답은 선언성이다. DOM 트리를 수동으로 순회하는 등의 저수준 테크닉 대신, 단순히 컴포넌트가 어떤 모습을 가져야 하는지 선언한다. React는 당신을 대신하여 저수준 작업을 해줄 것이고, 그 이면에서는 HTML DOM API 메서드가 호출된다. React는 당신이 아무 걱정도 하지 않게 해줄 것이다. 어쨌든 결국에, 컴포넌트는 마땅한 모습을 갖추게 될 것이다.

하지만 이것이 성능 문제를 해결해주지는 않는다. 그리고 바로 여기에서 가상 DOM이 나서게 된다.

가상 DOM

우선, 가상 DOM은 React가 발명한 것이 아니다. 하지만 React는 가상 DOM을 사용하며, 무료로 제공한다.

가상 DOM은 HTML DOM의 추상물이다. 가상 DOM은 가벼우며, 특정 브라우저의 세부 구현과 무관하다. DOM은 그 자체로 이미 추상물이기 때문에, 가상 DOM은 사실 추상물에 대한 추상물인 셈이다.

아마도 가상 DOM을 설명할 때, React가 만든, HTML DOM에 대한 단순화된 로컬 복사본이라고 설명하는 것이 좋을 것이다. 가상 DOM을 통하여 React는 이 추상화된 세상 안에서 계산을 수행하고, 으레 느리며 특정 브라우저에 특화되는 "실제" DOM 작업은 생략할 수 있다.

"평범한" DOM과 가상 DOM 간에는 큰 차이가 없다. React 코드의 JSX 부분이 단순 HTML과 유사한 모습을 보이는 이유이다.

var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});

대부분의 경우, 어떤 HTML 코드를 정적인 React 컴포넌트로 만들고 싶다면, 다음 과정을 거치면 된다:

  1. render의 결과로 HTMl 코드를 반환한다.
  2. class 특성의 이름을 classname으로 바꾼다. 왜냐하면 class는 JavaScript에서 예약어이기 때문이다.

가상 DOM과 일반 DOM 간에는 몇 가지 차이점이 더 존재한다.

  • 가상 DOM의 3가지 특성 - key, ref, dangerouslySetInnerHTML은 실제 DOM에 나타나지 않는다. 더 보기.
  • React의 가상 DOM에는 몇 가지 제약이 더 존재한다.

ReactElement vs ReactComponent

가상 DOM을 다룰 때에는, 위 두 가지의 차이점을 짚고 넘어가는 것이 아주 중요하다.

ReactElement

이것은 React의 주요 타입이다. React docs에 의하면,

ReactElement는 가볍고, 상태가 없으며, 불변성을 가진, DOM 요소에 대한 가상 표현이다.

ReactElement는 가상 DOM 속에 존재한다. 가상 DOM의 기본 노드들을 이루는 것이다. 불변성 덕분에 비교와 갱신이 쉽고 빨라진다. 이것이 React가 고성능인 이유이다.

ReactElement로 무엇이 될 수 있을까? 거의 모든 HTML 태그들 - div, table, strong 등... 전체 목록 보기.

ReactElement는 한번 정의되고 나면, "실제" DOM으로 렌더링 될 수 있다. 렌더링이 완료된 이후부터 React는 해당 요소에 대한 통제를 멈추게 된다. 느리고, 지루한 DOM 노드로 거듭나는 것이다.

var root = React.createElement('div');
ReactDOM.render(root, document.getElementById('example'));

JSX는 HTML 태그를 ReactElement로 컴파일한다. 따라서 아래의 코드는 위의 것과 동등하다:

var root = <div />;
ReactDOM.render(root, document.getElementById('example'));

다시 한번 말하지만, ReactElement는 React의 가상 DOM에서 기본 항목을 이룬다. 하지만, ReactElement는 기본적으로 상태가 없기 때문에, 개발자인 우리들에게는 썩 큰 도움이 되지 않아보인다. 우리는 변수나 상수 등이 들어있는, 마치 클래스처럼 생긴 HTML과 작업하곤 하지 않는가? 그렇다면...

ReactComponent

ReactComponentReactElement와 다른 점은, ReactComponent는 상태를 가진다는 점이다.

우리는 보통 React.createClass를 사용하여 ReactComponent를 정의한다.

var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});

render 메서드로 반환된 HTML스럽게 생긴 블록은 상태를 가질 수 있다. 가장 좋은 점은, 상태가 바뀔 때마다, 컴포넌트가 리렌더링된다는 것이다. (모두가 알고 있을 것이라 생각한다. 왜냐하면 이것이 React가 엄청난 이유이기 때문이다!)

var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});

ReactCompoenent는 동적 HTML을 디자인하는 데에 최고의 도구로 보인다. 가상 DOM에 접근 할 수 없음에도 손쉽게 ReactElement로 변환된다.

var element = React.createElement(MyComponent);
// or equivalently, with JSX
var element = <MyComponent />;

무엇이 차이를 만드는가?

ReactComponent는 아주 좋고, 관리하기 쉽다. 하지만 가상 DOM에는 접근할 수 없다. 이런 점을 최대한 활용해야 한다.

ReactComponent가 상태를 변화시킬 때마다, "실제" DOM에는 최소한의 변화만을 만들고 싶다. 이것이 React가 일을 처리하는 방식이다. ReactComponent는 ReactElement로 변환된다. 여기서 ReactElement는 가상 DOM에 주입되고, 비교 및 갱신이 쉽고 빠르게 이루어진다. 어떻게? 그것은 diff 알고리즘이 알아서 할 일이다. 핵심은, 통상적인 DOM보다 훨씬 빠르다는 것이다.

요약

가상 DOM이 첫번째 화면에서 자랑할 만한 기능인가? 내가 보기에는 그렇다. 실제로, React의 성능은 절대적으로 높고, 가상 DOM은 거기에서 큰 도움이 된다.


덧글

댓글 입력 영역