이렇게 빨리 리액트 포스팅을 하게 될 줄이야.
순서는 "리액트를 다루는 기술 - 김민준"을 따라서 진행할 것이다.
목차
1. JS의 한계
2. What is React?
3. Virtual DOM & Fiber
1. JS의 한계
Frontend의 시작은 HTML/CSS에 JavaScript를 곁들인 정도에 불과했다.
예전엔 정적인 페이지에 동적인 장치나 연산을 수행하는 단순 스크립트로 취급되었었다면,
현재는 JavaScript를 빼놓고는 프론트 엔드를 논할 수가 없게 되었다.
(반쪽자리 언어 취급은 여전히 바뀌지 않았다. 흑흑)
하지만 점점 웹 서비스 플랫폼의 규모가 방대해질 수록 JS로 모든 기능을 처리하는 것에 한계가 오기 시작했다.
JQuery도 요새는 사용하지 않는 추세인 걸 감안하면, 결과적으로 얘도 커버하지 못할 규모였다는 뜻이다.
(이게 사실 중요한 이야기다. 리액트의 개발 목적은 대규모 플랫폼을 대상으로 하기 때문에
소규모 프로젝트나 서비스 경우에는 오히려 JS를 최적화 시키는 게 훨씬 빠르게 동작할 수 있다.)
결국 이 문제를 해결하기 위해 여러 프레임 워크(Angular, Vue etc..)들이 대거 등장했는데,
이들 프레임 워크의 공통점이 Model과 View가 존재하고 있었고
View의 값을 수정하기 위해선 Controller가 Model로 부터 데이터를 조회 및 수정하고 다시 View에 반영해야 했다.
문제는 View에서 값을 수정하고자 하는 요소를 찾아내는데..서비스 규모가 커질 수록
간단한 논리 규칙만으로 접근하기엔 탐색이 오래 걸리고, 구조가 복잡해진다는 단점은 여전했다.
그리고 나름 규모있는 서비스들의 코드를 훑어보면 그나마 짧은 게 5000줄 언저리인데,
관리라도 소홀히 했다가는 스파게티 괴물이 탄생하기 십상이다.
그러던 어느날 페이스북 개발팀에서 한 가지 아이디어를 내놓았는데,
데이터가 변화할 때마다 기존의 View를 통채로 날려버리고 그냥 처음부터 새로 렌더링하는 방식이었다.
렌더링이란?
서버로부터 HTML 파일을 받아서 브라우저에 뿌려주는 과정
1) 렌더링 엔진은 HTML 문서를 파싱(parsing)해서 DOM 트리를 형성한다.
2) 외부 css 파일과 함께 css 스타일 요소를 파싱하여 CSSOM 트리를 만든다.
3) DOM 트리와 CSSOM을 결합하여 렌더링 트리를 형성한다.
4) 렌더링 트리 스타일 규칙에 따라 노드위 위치와 규모를 계산하고 배치할 좌표를 설정한다.
5) 렌더링 엔진은 HTML이 모두 파싱될 때까지 기다리는 게 아니라 배치가 시작되면
동시에 렌더링 트리 루트부터 시작하여 전체를 순회하며 페인팅(레스터화)을 진행한다.
SW에 대해 무지한 상태면 굉장히 쉬워보이고 간단한 방법같지만 실제로는 그렇지 않다.
DOM(The Document Object Model, 문서 객체 모델)은 <html>이나 <body>같은 html 태그들을
JS가 사용할 수 있도록 객체로 만들어주는 역할을 함으로써 페이지의 정보를 제공하는데,
구조는 아래 블로그를 참조하면 된다.
구현이 힘든 것이 아니라 너무 느리다. 그리고 메모리도 많이 잡아 먹는다.
보통 시간 복잡도를 최소화하기 위해 메모리를 희생하는 것이 알고리즘의 베이스인데,
DOM은 이미 메모리도 많이 사용한다.
가끔 DOM이 느리다는 표현을 사용하는 곳도 있는데, 인접 리스트는 그렇게 느리지 않다..
단지 처음부터 렌더링 하려고 CSS 레이아웃을 입히는 과정에서 시간이 오래 걸리는 것이다.
그런데 여기서 데이터가 수정될 때마다 렌더링을 하면 CPU 점유율이 급상승할 것이고
input 태그에 text를 입력할때마다 렌더링을 하면 반드시 끊김 현상이 발생하게 될 것이다.
??? 그럼 이건 불가능한 거 아닌가요???
그런데 페이스북 개발자는 그걸 해냈다. (역시 대기업)
2. What is React?
최근 React를 단순히 View 라이브러리라고 한다면 받아들일 사람이 몇이나 될까?
단순 라이브러리라고 하기엔 setState에 lifecycle method, hook.. 초기 컨셉을 잃어버린지는 오래다.
어쨌든 지금은 이론 공부이므로 이론에 치중하자.
리액트는 View만 잡고 패는 라이브러리다. 이전의 프레임워크와는 다르다.
프레임 워크는 설계도가 마련되어 있고 그 설계도를 따라 만드는 것이지만,
라이브러리는 도서관에서 책을 꺼내 쓰듯이 자유롭게 설계하고 구현할 수 있다.
직접 프로젝트를 해보면 쉽게 이해할 수 있는 내용이다.
리액트에는 특정 부분이 어떻게 생길지 정하는 선언체가 있는데, 이를 컴포넌트(Component)라 한다.
Header - Navigation - Contents - Footer 구조의 사이트를 선언해놓고 해당 컴포넌트에서 생김새와 작동 방식을 정의한다.
여기서 중요한 것은 컴포넌트마다 분류해놓았기 때문에, 값이 변경되면 해당 요소만 새롭게 '갈아 끼우면' 되는 것이다.
(업데이트보다는 조화 과정(reconciliation)을 거친다라는 표현이 더 적절하다고 한다. 리렌더와는 또 사뭇 다르다.)
그런데 처음에 브라우저에 렌더링하기 위해서는 DOM을 사용해야 한다고 했는데,
어떻게 update가 필요한 노드를 파악할 것이며, 또 수정할 것인가?
이를 위해서 리액트는 Virtual DOM을 사용한다.
3. Virtual DOM & Fiber
DOM은 알겠는데 Virtual DOM은 또 뭘까?
DOM(The Document Object Model, 문서 객체 모델)의 동적 UI에 취약한 단점을 보완하고자
최소한의 조작으로 DOM 업데이트를 수행하는 방법이다.
더 정확하게 언급하자면 Virtual DOM은 Real DOM의 in-memory 표현이고 특정 기술이 아니다.
리액트 알고리즘을 재구성한 재조정 엔진, Fiber로 Virtual DOM을 탐색/우선 순위 지정하는 것.
Virtual DOM의 Reconciliation Process
1. 초기 렌더링으로 DOM 트리를 만든다.
2. 업데이트(state나 props의 변화)가 필요해지면 전체 UI를 Virtual DOM에 리렌더링한다.
3. 기존의 Virtual DOM의 내용과 비교하여 update가 필요한 노드에 flag를 발생시킨다.
4. 렌더가 필요한 노드를 빠르게 탐색하여 바뀐 부분만 실제 DOM에 적용한다.
렌더 함수가 실행되고 화면에 요소가 표시되는 그 사이에 발생하는 과정이다.
1. Render Phase
2. Commit Phase
이게 뭔가 싶지만 아무튼 Virtual DOM이라고 치자.
Real DOM은 브라우저에 화면을 표시하고 있는데, 이를 그때그때 작업하려고 시도하면 무겁고 속도도 느리며,
리스크가 너무 크다.
따라서 Render Phase 단계에서 변경 사항을 감지하면 Virtual DOM을 만들어 update하여
alternate virtual DOM tree을 생성하고, diff를 통해 Real DOM에 빠르게 적용하기 위한 effects list를 만든다.
최종적으로 Real DOM에 최소한의 변경사항(effects list)만 적용하여 효율을 올린다.
DOM 객체의 변화가 감지되면 해당 노드에 flag를 세우고 트리를 순회하면서 탐색을 실시한다.
여기서 이미 Render Phase가 시작되었으며 다음은 그 과정이다.
렌더가 필요한 노드부터 순차적으로 beginWork()함수가 실행되면서 함수가 재귀적으로 호출된다.
마지막에 completeWork() 함수를 호출하면 diffing alorithm으로 effect list를 생성한다.
여기까지 하면 Render Phase가 끝나고 Commit Phase 단계로 넘어간다.
Alternate Tree를 재구성하기 시작한다. 다만 여기서 origin과 alternate의 J와 E 노드는 서로 다른 객체다.
하지만 memorized component의 경우 F처럼 같은 포인터를 공유하여 같은 객체임을 명시할 수 있다.
React.memo를 통해 리렌더링을 방지시키는 셈이다.
B노드에 도달하여 Alternate Tree를 만들 때, DOM 속성 변경을 함으로써 Alternate Tree를 완성한다.
변경사항 반영이 끝나고 새로운 VDOM을 current Tree로 가지다가 Real DOM에 반영시킨다.
그런데 과정을 보고 나니 꼭 VDOM이 효율적일 것 같지도 않다. 사실이다.
애초에 React는 대규모 플랫폼을 관리하기 위함이지 소규모 사이트에서는 마크업 최적화를 실시하는 것이
훨씬 효율성 측면에서 나을 수도 있다.