프론트엔드(FE) 개발자가 브라우저(Br) 동작 원리를 알아야 하는 이유
- FE에게 Br는 거의 모든 것.
- Br를 통해 개발, Br를 통해 테스트 및 배포 진행, 사용자들은 Br를 통해 웹에 접속
- FE는 Br의 모든 것이 아닌 HTML, CSS, JS가 의도대로 동작하는지 확인해도 큰 문제x
- 하지만 기능이 많고 다양한 동작이 발생한다면 최적화가 필요해짐
- Br의 최적화를 위해선 결국 Br의 동작 원리를 이해하는 것에서부터 시작
- 해당 포스트에서 알아볼 것 => Br의 렌더 과정 & 원리 ( 아래는 선 4줄 요약 )
- HTML로부터 DOM 트리를, CSS로부터 CSSOM 트리를 빌드
- DOM & CSSOM을 결합하여 렌더 트리를 형성
- 렌더 트리에서 레이아웃을 실행 => 각 노드의 기학학적 형태를 계산
- 개별 노드를 화면에 페인트
브라우저 컴포넌트
- 사용자 인터페이스(UI)
- Br에서 볼 수 있는 거의 모든 것.
- 요청한 페이지를 보여주는 창 외의 모든 UI
- eg) 주소창, 뒤로가기, 앞으로가기, 새로고침,북마크, 환경설정
- 브라우저 엔진
- UI 와 렌더링 엔진 사이에서의 중재자 역할
- eg) UI에 있는 새로고침을 누르면, 브라우저 엔진은 이를 이해하고 새로고침 명령을 수행함
- 렌더링 엔진 ( 이 글에서 우리가 알아볼 것)
- HTML & CSS & JS를 파싱하고, 그 결과물을 페이지에 그려내는 역할
- 브라우저마다 다양한 엔진 존재
- Chrome, Opera, Edge: Blink
- Firefox: Gecko
- Safari: Webkit
- 네트워크:
- HTTP(S) 프로토콜을 이용해 외부의 리소스를 얻어오고, 서버에 요청을 보낼때 사용
- JS 인터프리터
- JS 해석 및 실행
- eg) Chrome: 구글의 V8
- UI 백앤드
- 브라우저가 동작하고 있는 OS의 인터페이스를 따르는 UI들을 처리
- eg) 얼럿 || 셀렉트 박스가 OS별로 다르게 동작
- 자료 저장소
- 브라우저에서의 데이터를 로컬에 저장하기 위한 레이어
- eg) 쿠키, 로컬, 세션, indexedDB, 웹SQL, 파일 시스템 등
렌더링 엔진의 동작 과정
- 유저가 웹에 접속하면, 네트워크를 통해 HTML 문서를 얻어 올 수 있다.
- 그러면 렌더링 엔진은 HTML 문서를 해석함. (브라우저 엔진마다 방식 조금씩 다르지만 큰 틀은 같다)
- 파싱
- 렌더 트리 구축
- 레이아웃 || 리플로우
- 페인트
- 이러한 과정을 일컫어 중요 렌더링 경로 라고 부름. (초당 60회 정도의 주기로 계산, 3-4번이 비용 가장 많이 듬)
- 웹 페이지의 반응 속도는 각 단계에서 리소스를 로드하는 순서나 스크립트 내용에 따라 차이가 생김
- 위의 과정들을 최적화 시키는 것이 곧 렌더링 시간을 개선시키는 것
중요 렌더링 경로의 각 단계별 정리
1. 파싱
- 파싱이란 토큰화된 코드를 구조화하는 과정 (cf 토큰화란 최소 단위로 코드를 쪼개는 것을 의미)
- eg) <div></div> 라는 코드를 토큰화하면, ['<', 'div ', '>','<', 'div ', '/', '>' ] 처럼 나타냄
- 파싱과정이란 입력받은 문자열이 정해진 문법들을 모두 따르나 확인하는 과정
- 파서(parser)는 이러한 파싱 과정을 전문적으로 해주는 부분
- HTML & CSS는 렌더링 엔진이 해석 & JS는 JS해서기라는 별도 레이어에서 해석
- 렌더링 엔진에서는 HTML과 CSS를 파싱
- 아래는 HTML 파싱 흐름도
HTML 파싱
- 브라우저는 토큰화된 HTML의 문자열들을 이용해 파스 트리를 생성
- 파스 트리: 브라우저가 읽어야 할 HTML 코드를 트리 모양으로 구조화한 것
- 이후 브라우저는 파스 트리를 통해 DOM(Document Object Model) 트리를 만듬.
- 파스트리 : 토큰화된 문자열을 단순하게 구조화 한것
- DOM 트리 : 실제로 상호 작용 할 수 있는 HTML 엘리먼트로 이루어진 트리 (JS로 상호작용하는 트리)
- HTML 파서의 특징
- 오류에 너그러운 속성 ( forgiving nature) - HTML 파싱도중 에러 발생시, 브라우저가 자체적으로 복구 시도
- 파싱 과정이 중단 될 수 있음. - <script>, <link> 같은 외부 태그를 만나게 되면 HTML 파싱 즉시 중단 후 태그 해석
- 이러한 이슈를 해결하기 위해 Async와 Defer 속성이라는 옵션 제공됨
- 재시작 (ReEntrant) -
- 어떠한 요인으로 파싱이 중단되는 듯 방해가 생기면 DOM의 변화가 생길 수 있음
- 이러한 경우 HTML은 처음부터 다시 파싱 과정을 거침. -> 파싱 시간 증가 요인
CSS 파싱
- CSS 파싱은 공식적인 명세가 있기에 HTML에 비해 복잡x
- 보통 CSS를 링크하는 코드가 HTML 코드 내에 삽입되어 있어서 HTML파싱 도중에 CSS파싱 시작됨
- 네트워크를 통해 먼저 받아온 코드부터 해석하는 HTML파서와는 달리. 전체 파일을 다운로드 한 후 파싱 시작
- CSS 파싱 과정이 끝나면, 코드에서 명세한 내용과 순서를 바탕으로 CSSOM 트리를 구조화 함(CSS Object Model)
- 해당 트리에는 스타일, 규칙, 선택자 등의 정보가 노드에 들어가있음
2. 렌더 트리
- DOM 트리가 구성되는 동안 브라우저는 렌더 트리(프레임 트리) 를 구성하기 시작
- 렌더 트리는 화면에 나타나는 요소들을 결정하는 트리 -> 보여질 요소들, 적용될 스타일, 나타낼 순서 명세화
- 렌더 트리 = DOM트리 + CSSOM 트리 -> <head>,<script>, display:none 등 화면에 보이지 않는 요소들은 렌더 트리x
- 따라서 렌더 트리는 DOM+CSSOM 와 정확하게 1:1 매칭 x
3. 레이아웃 || 리플로우
- 렌더 트리 구성이 끝나면 레이아웃 단계 시작 ( 모질라에서는 reflow 라고 부름 )
- 이 단계에서는 렌더 트리에서 계산되지 않았던 노드들의 크기, 레이어간 순서 같은 정보를 계산하여 좌표에 나타냄
- 이 과정은 HTML의 root Obj 로부터 재귀적으로 실행됨
- 계산 범위에 따라 Global(전역적) Layout , Incremental(증분적) Layout 으로 구분됨
- 전역적 레이아웃
- 화면 전체의 레이아웃을 계산하는 것.
- 폰트 및 사이즈, 뷰포트의 사이즈 변경 등 새롭게 적용되는 것이 있다면 전체 레이아웃 다시 계산
- 전역적 레이아웃은 모든 렌더 트리에 대해 기학학적 계산을 수행하므로 속도 저하 요인이 됨(브라우저는 최적화 로직 탑재 되어 있음) - eg) Dirty bit system - 특정 부분만 다시 계산하여 리소스 낭비 줄임
- 증분적 레이아웃
- 위에서 말한 더티 비트 시스템을 활용한 것.
- 렌더 트리를 재귀적으로 탐색하다가, 레이아웃 변경 요소를 만나면 스케줄러를 통해 비동기로 일괄 작업
- 이러한 브라우저의 최적화만으로는 복잡한 레이아웃을 감당 못하기 때문에 개발자가 신경써야함
- DOM의 레이아웃과 관련 값에 변화를 주는 JS 코드 작성시에는 최대한 묶어주기
- 전역적 레이아웃
4. 페인트
- 말 그대로 레이아웃 단계를 통해 화면에 배치된 요소들에게 색을 입히고 레이어의 위치를 결정하는 단계
- root Obj로부터 재귀적으로 실행됨
- 전역적&증분적 페인팅으로 나뉨
- 스타일이 복잡할수록 페인팅 시간 늘어남
가상 DOM
- 가상 DOM이란?
- React & Vue 를 써봤다면 익숙한 용어
- 가상 DOM은 실제로 렌더링 되지 않지만, 실제 DOM 구조를 반영한 상태로 메모리에 있는 가상의 DOM이다
- 기존 DOM의 문제점
- 중요 렌더링 경로 중 가장 많은 비용이 드는 것은 레이아웃&페인팅 단계이므로 이 연산을 최소화 하는 것이 성능 최적화
- JS를 이용해 DOM을 직접 조회하면, 변경 사항 있을때마다 윗 단계들을 다시 초래함.
- 가상 DOM으로 이슈 해결
- 가상 DOM은 실제 화면에 그릴 필요가 없어서 실제 DOM 보다는 연산 비용 적음
- 변경 사항들을 한번에 묶어서 실제 DOM에 반영
- 레이아웃&페인트 단계에서 한번에 변경되는 점은 많아지겠지만 연산은 최소화됨.
- 가상 DOM의 자동화로 관리 포인트를 줄여줌. (React, Vue )
https://wormwlrm.github.io/2021/03/27/How-browsers-work.html 님의 글을 거의 복붙 수준으로 작성
'FrontEnd > React.js' 카테고리의 다른 글
[React] 컴포넌트가 데이터를 다루는 3가지 방법 (0) | 2023.06.21 |
---|---|
[React] 데이터를 실시간으로 갱신하기(useSWR, setInterval,useQuery) (0) | 2023.03.19 |
[React.js] React Query 에 대해 (feat. SWR 과의 차이) -v3 (0) | 2022.12.30 |
[React.js] video 태그에 동적 값 할당 (0) | 2022.12.29 |
React-Query에 대하여 ( useQuery, useMutation ) (0) | 2022.12.07 |