가상 DOM에 대해 가지고 있는 한 가지 일반적인 오해는 리액트의 이러한 방식이 일반적인 DOM을 관리하는 브라우저보다 빠르다는 사실이다. 이는 리액트 개발자인 댄 아브라모프(dan_abramov)가 사실이 아니라고 부정한 바 있다. 무조건 빠른 것이 아니라 가상 DOM 방식은 대부분의 상황에서 왠만한 app을 만들 수 있을 정도로 충분히 빠르다는 것이다.
리액트에서 관리하는 평범한 자바스크립트 객체. 파이버는 파이버 재조정자(fiber reconciler)가 관리하는데, 이는 가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하며, 둘 사이에 차이가 있으면 변경에 관련된 정보를 가지고 있는 파이버를 기준으로 화면에 렌더링을 요청하는 역할을 함.
재조정(reconciliation) : 가상 DOM과 실제 DOM을 비교하는 작업(알고리즘)
파이버 트리는 두 가지 종류임. 1. 현재 모습을 다음 파이버 트리. 2. 작업중인 상태를 나타내는 workInProgress 트리
리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해서 workInProgress 트리를 현재 트리로 바꿈. 이러한 기술을 더블 버퍼링이라고 함. (컴퓨터 그래픽 분야에서 사용되는 용어)
일반적인 파이버 노드의 생성 흐름은 다음과 같다.
1. 리액트는 beginWork() 함수를 실행해 파이버 작업을 수행하는데, 더 이상 자식이 없는 파이버를 만날 때까지 트리 형식으로 시작
2. 1번에서 작업이 끝난다면 그다음 completeWork() 함수를 실행해 파이버 작업을 완료함
3. 형제가 있다면 형제로 넘어감
4. 2번, 3번이 모두 끝났다면 return으로 돌아가 자신의 작업이 완료됐음을 알림
<A1>
<B1>안녕하세요</B1>
<B2>
<C1>
<D1 />
<D2 />
</C1>
</B2>
<B3 />
</A1>
위 작업은 JSX 코드에서 다음과 같이 수행된다.
- A1의 beginWork()가 수행
- A1은 자식이 있으므로 B1로 이동해 beginWork()를 수행
- B1은 자식이 없으므로 completeWork()가 수행. 자식은 없으므로 형제인 B2로 넘어감
- B2의 beginWork()가 수행. 자식이 있으므로 C1로 이동
- C1의 beginWork()가 수행. 자식이 있으므로 D1로 이동
- D1의 beginWork()가 수행
- D1은 자식이 없으므로 completeWork()가 수행. 형제인 D2로 이동
- D2는 자식이 없으므로 beginWork() 수행
- D2는 자식, 형제 없으므로 위로 이동해 D1, C1, B2 순으로 completeWork() 호출
- B2는 형제인 B3로 이동 beginWork() 수행
- B3의 completeWork()가 수행되면 반환해 상위로 타고 올라감
- A1의 completeWork()가 수행.
- 루트 노드 완성되는 순간 최종적으로 commitWork() 수행되고 이 중에 변경 사항을 비교해 업데이트가 필요한 변경 사항이 DOM에 반영
이후 리렌더링 시에는 이미 있는 파이버를 재사용한다.
리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이뤄짐.
실제 브라우저 구조인 DOM에 반영하는 것은 동기적으로 일어나야 하고, 또 처리하는 작업이 많아 불완전하게 표시할 가능성이 높기 때문에 이러한 작업을 가상에서 먼저 수행해 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것.
가상 DOM이라는 표현을 계속 썼지만, 이는 오직 Web app에서만 통용되는 개념이다. 리액트 파이버는 리액트 네이티브와 같은 브라우저가 아닌 환경에서도 사용할 수 있기 때문에 파이버와 가상 DOM은 동일한 개념이 아니다.
리액트와 리액트 네이티브의 렌더러가 서로 다르다 하더라도 내부적으로 파이버를 통해서 조정되는 과정은 동일하기 때문에 동일한 재조정자를 사용할 수 있게 된다.
가상 DOM과 리액트의 핵심은 브라우저의 DOM을 더욱 빠르게 그리고 반영하는 것이 아니라 바로 값으로 UI를 표현하는 것. 또한 이러한 흐름을 효율적으로 관리하기 위한 메커니즘이 바로 리액트의 핵심이다.