- Published on
React Deep Dive
- Authors

- Name
- JaeHyeok CHOI
- none
JSX란?
JSX는 자바스크립트 표준 코드가 아닌 페이스북이 임의로 만든 새로운 문법이기 때문에 JSX는 반드시 트랜스파일러를 거쳐야 비로소 자바스크립트 런타임이 이해할 수 있는 의미있는 자바스크립트 코드로 변환된다.
JSX의 목적
HTML이나 XML을 JS 내부에 표현하는 것 뿐만 아니라 다양한 트랜스파일러에서 다양한 속성을 가진 트리 구조를 토큰화하여 ECMAScript로 변환하는데 초점을 두고 있다.
결국 JSX 내부에 트리 구조로 표현하고 싶은 다양한 것들을 작성해 두고, 이 JSX를 트랜스파일이라는 과정을 거쳐 ECMAScript 가 이해할 수 있는 코드로 변경하는 것이 주 목적이다.
즉, JSX가 사용되는 곳은 리액트 내부에서 변환하는 HTML과 자바스키릅트 코드이나, 꼭 그것만 한정되어 있는 것은 아니라는 것이다.
JSX의 정의
JSX는 기본적으로 JSXElement, JSXAttributes, JSXChildren, JSXStrings 라는 4가지 컴포넌트를 기반으로 구성된다.
JSXElement
가장 기본이 되는 요소로, HTMl의 요소와 비슷한 역할을 한다.
- JSXOpeningElement : <Component>
- JSXClosingElement : </Component>
- JSXSelfClosingElement : <Component />
- JSXFragment : <></>
JSXElementName
- JSXIdentifier
- JSXNamespacedName: <foo:bar></foo:bar> ← 이런 식으로 JSXIdentifier:JSXIdentifier 의 조합하여 사용이 가능하다. (단, `:` 로 묶을 수 있는 것은 한 개 뿐이다.)
- JSXMemberExpression: <foo.bar.baz></foo.bar.baz> ← JSXIdentifier.JSXIdentifier 의 조합으로 다른 식별자로 이어주는 것도 가능하다.
JSXAttributes
Element에 부여할 수 있는 속성이다.
- JSXSpreadAttributes: 자바스크립트의 전개 연산자와 동일한 역할을 한다.
- JSXAttributes: 속성을 나타내는 키와 값으로 짝을 이루어서 표현한다. 키는 JSXAttributeName, 값은 JSXAttributeValue 로 불린다.
- JSXAttributeName: 속성의 키 값. 키로는 앞서 JSXElementName에서 언급했던 JSXIdentifier와 JSXnamespaceName이 가능.
- JXSAttributeValue:속성의 키에 할당할 수 있는 값으로, 다음 중 하나를 만족해야 한다.
- 큰/작은 따옴표로 구성된 문자열
- { AssignmentExpression }
- JSXElement
- JSXFragment: 값으로 별도 속성을 갖지 않는 형태의 JSX 요소가 들어갈 수도 있다
JSXChildren
JSXElement의 자식 값을 나타낸다.
- JSXChild
- JSXElement
- JSXFragment
- { JSXChildExpression (optional) }
JSXStrings
HTML에서 사용하는 일반적인 문자열도 가능하다.
JSX는 어떻게 JS에서 변환될까?
@babel/plugin-transform-react-jsx 플러그인은 JSX 구문을 JS가 이해할 수 있는 형태로 변환한다.
'use strict'
var ComponentA = React.createElement(
A,
{
required: true,
},
'Hello World'
)
var ComponentB = React.createElement(React.Fragment, null, 'Hello World')
var ComponentC = React.createElement('div', null, React.createElement('span', null, 'Hello World'))
JSX 반환 값이 결국 React.createElement 로 귀결된다.
가상 DOM과 리액트 파이버
DOM과 브라우저 렌더링 과정
DOM이란?
웹페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 담고 있다.
브라우저가 웹사이트 접근 요청을 받고 화면을 그리는 과정
- 브라우저가 사용자가 요청한 주소를 방문해 HTML 파일을 다운로드한다.
- 브라우저의 렌더링 엔진은 HTML을 파싱해 DOM 노드로 구성된 트리(DOM)을 만든다.
- 2번 과정에서 CSS 파일을 만나면 해당 CSS 파일도 다운로드한다.
- 브라우저의 렌더링 엔진은 이 CSS도 파싱하여 CSS 노드로 구성된 트리 (CSSOM)를 만든다.
- 브라우저는 2번에서 만든 DOM 노드를 순회하는데, 여기서 모든 노드를 방문하는 것이 아닌 사용자 눈에 보이는 노드만 방문한다. 즉, display: none 과 같이 사용자 화면에 보이지 않는 요소는 방문해 작업하지 않는다.
- 5번에서 제외된 눈에 보이는 노드를 대상으로 해당 노드에 대해 CSSOM 정보를 찾고 여기서 발견한 CSS스타일 정보를 이 노드에 적용한다. 이 DOM 노드에 CSS를 적용하는 과정은 크게 두 가지로 나눌 수 있다.
- 레이아웃: 각 노드가 브라우저 화면의 어느 좌표에 정확히 나타나야 하는지 계산하는 과정.
- 페인팅: 레이아웃 단계를 거친 노드에 색과 같은 실제 유효한 모습을 그리는 과정
이렇게 모든 단계를 거친 최종 출력물은 웹 앱의 모든 컨텐츠와 스타일 정보를 갖게 된다.
가상 DOM 탄생 배경
위의 DOM을 생성하는 과정은 특정한 요소만을 수정하게되면, 레이아웃이 일어나고 이는 필연적으로 리페인팅이 발생하게 된다. 이렇게 되면 하위 자식 노드들도 변경이 일어나게 되는데, 이는 엄청난 비용이 발생하게 된다.
이러한 문제점을 해결하기 위해 탄생한 것이 바로 가상 DOM 이다. 가상 DOM은 웹페이지가 표시해야할 DOM을 일단 메모리에 저장하고 리액트가 실제 변경에 대한 준비가 완료되었을 때 실제 브라우저의 DOM에 반영한다. (정확히는 react-dom이 이를 수행한다.)
가상 DOM을 위한 아키텍처, 리액트 파이버
가상 DOM을 만드는 과정을 리액트는 어떻게 처리할까? 리액트는 여러 번의 렌더링 과정을 압축하여 어떻게 최소한의 렌더링 단위를 만들어 내는 것인가?
리액트 파이버?
리액트에서 관리하는 평범한 자바스크립트 객체이다. 파이버는 파이버 재조정자가 관리하는데, 이는 앞서 이야기한 가상DOM과 실제DOM을 비교해 변경 사항을 수집하며 둘 사이에 차이가 있을 경우 변경에 관련된 정보를 가지고있는 파이버를 기준으로 화면에 렌더링을 요청하는 역할을 한다.
재조정 이라는 용어가 낯설겠으나, 리액트에서 어떤 부분을 새롭게 렌더링해야 하는지 가상 DOM과 실제 DOM을 비교하는 작업(알고리즘)이라고 이해하자.
파이버는 다음과 같은 작업을 한다.
- 작업을 작은 단위로 분할하고 조갠 다음, 우선순위를 매긴다.
- 이러한 작업을 일시 중지하고 나중에 다시 시작할 수 있다.
- 이전에 했던 작업을 다시 재사용하거나 필요하지 않은 경우에 폐기할 수 있다.
이러한 작업은 모두 비동기로 일어난다. 과거 리액트의 조정 알고리즘은 스택으로 이뤄져 있었으나, JS의 특징인 싱글 스레드라는 점으로 인해 이 동기 작업은 중단될 수 없고, 다른 작업이 수행되고 싶어도 중단할 수 없었다. 이는 리액트의 비효율성으로 이어졌다.
파이버의 구현
파인버는 일단 하나의 작업 단위로 구성돼 있다. 리액트는 이러한 작업 단위를 하나씩 처리하고 finishedWork()라는 작업으로 마무리한다. 그리고 이 작업을 커밋해 실제 브라우저 DOM에 가시적인 변경사항을 만들어내며 이 단계는 아래 두 단계로 나눌 수 있다.
- 렌더 단계에서 리액트는 사용자에게 노출되지 않는 모든 비동기 작업을 수행한다. 그리고 이 단계에서 앞서 언급한 파이버의 작업, 우선순위를 지정하거나 중지시키거나 버리는 등의 작업이 일어난다.
- 커밋 단계에서는 앞서 언급한 것처럼 DOM에 실제 변경 사항을 반영하기 위한 작업, commitWork()가 실행되는데, 이 과정은 앞서와 다르게 동기식으로 일어나고 중단될 수도 없다.
…
이렇게 생성된 파이버는 state의 변경, 생명주기 메서드가 실행, DOM의 변경이 필요한 시점에 실행된다. 그리고 이 파이버를 처리할 때마다 이러한 작업을 직접 바로 처리하기도, 스케쥴링하기도 한다.
리액트의 핵심 원칙은 UI를 문자열, 숫자, 배열과 같은 값으로 관리한다는 것이다. 변수에 이러한 UI 관련 값을 보관하고, 리액트의 자바스크립트 코드 흐름에 따라 이를 관리, 표현하는 것이 바로 리액트이다.
리액트 파이버 트리
- 현재 모습을 담은 파이버 트리
- 작업 중인 상태를 나타내는 workInProgress 트리
리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해 workInProgress 트리를 현재 트리로 바꿔버린다. (더블 버퍼링) 이는 커밋 단계에서 수행된다. UI 렌더링을 위해 존재하는 트리인 current를 기준으로 모든 작업이 시작되며 업데이트 발생시 파이버는 리액트에서 새로 받은 데이터로 새로운 workInProgress트리를 빌드하기 시작한다. 빌드가 끝나면 다음 렌더링에 이 트리를 사용한다. workInProgress 트리가 UI에 최종적으로 렌더링되어 반영이 완료되면 current가 이 workInProgress로 변경된다.
파이버 작업 순서
- 리액트는 beginWork() 함수를 실행해 파이버 작업을 수행, 자식이 없는 파이버를 만날 때까지 트리 형식으로 시작된다.
- 1번 작업이 끝나면 compleeteWork() 함수를 실행해 파이버 작업을 완료한다.
- 형제가 있다면 형제로 넘어간다.
- 2, 3번이 모두 끝나면 return으로 돌아가 자신의 작업이 완료됐음을 알린다.
파이버와 가상 DOM
리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이루어진다. 이와 달리 실제 브라우저 구조인 DOM에 반영하는 것은 동기적으로 일어나야하고, 또 처리하는 작업이 많아 화면에 불완전하게 표시될 수 있는 가능성이 높으므로 이러한 작업을 메모리상에서 먼저 수행하여 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것이다.
리액트 파이버는 가상 DOM과 동일한 개념이 아니다. 리액트 파이버는 리액트 네이티브와 같이 다른 환경에서도 사용할 수 있다.
가상 DOM은 실제 DOM의 가벼운 복사본으로, 변경 사항을 메모리에 저장하고 실제 DOM과 비교하여 효율적으로 업데이트하는 데 사용됩니다. React Fiber는 가상 DOM을 기반으로 하는 리액트의 새로운 재조정 엔진으로, 비동기적으로 작업을 처리하고 렌더링 성능을 향상시키는 역할을 합니다.