XML
과 유사한 내장형 구문. JSX
를 꼭 리액트에서만 사용하라는 법은 없으니 기억해두자.JSX 설계 목적?
다양한 트랜스파일러에서 다양한 속성을 가진 트리 구조를
토큰화
하여 ECMAScript로 변환하는 데 초점을 두고 있음.
핵심
➡️ JSX
는 자바스크립트 내부에서 표현하기 까다로웠던 XML
스타일의 트리 구문을 작성하는 데 많은 도움을 주는 문법이다.
JSXElement
,JSXAttributes
,JSXChildren
,JSXStrings
JSXElement
1-1. JSX
를 구성하는 가장 기본 요소. HTML의 요소(element)와 비슷한 역할을 함. 해당 컴포넌트의 조건은 다음과 같다.
<JSXOpeningElement>
|<JSXClosingElement>
|<JSXSelfClosingElement>
|<JSXFragment>
JSXAttributes
2-1. JSXElement
에 부여할 수 있는 속성을 의미한다.
JSXChildren
3-1. JSXElement
의 자식 값을 나타낸다. JSX는 속성을 가진 트리 구조를 나타내기 위해 만들어졌기 때문에 JSX로 부모와 자식 관계를 나타낼 수 있으며, 그 자식을 JSXChildren
이라고 한다.
JSXStrings
4.1 HTML
에서 사용 가능한 문자열은 모두 JSXStrings
에서도 사용 가능하다.
XML
의 코드 스타일// xml
<Person>
<FirstName>John</FirstName>
<LastName>Doe</LastName>
<Age>30</Age>
</Person>
JSX
와 매우 유사한 모습인 것을 볼 수 있음.
JSX
반환값은 결국 React.createElement
로 귀결된다.리액트의 가상 DOM이 무엇인지, 그리고 실제 DOM에 비해 어떤 이점이 있는지, 가상 DOM을 다룰때, 무엇을 주의해야 하는가?
DOM
은 웹페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 가지고 있다.
✅ 브라우저가 웹사이트 접근 요청을 받고 화면을 그리는 과정은 다음과 같다.
1️⃣. 브라우저가 사용자가 요청한 주소를 방문해 HTML 파일을 다운로드한다.
2️⃣. 브라우저의 렌더링 엔진은 HTML을 파싱해 DOM 노드로 구성된 트리(DOM Tree
)를 만든다. //파싱 === 해석
3️⃣. 2번 과정에서 CSS 파일을 만나면 해당 CSS 파일을 다운로드한다.
4️⃣. 브라우저의 렌더링 엔진은 이 CSS도 파싱해 CSS 노드로 구성된 트리(CSSOM
)을 만든다.
5️⃣. 브라우저는 2번에서 만든 DOM노드를 순회하는데, 여기서 모든 노드를 방문하는 것이 아니다. 사용자 눈에 보이는 노드만 방문한다.
display : none;
과 같이 사용자 화면에 보이지 않는 요소는 방문하지 않음.
이는 트리를 분석하는 과정을 조금이라도 빠르게 하기 위함이다.
6️⃣. 눈에 보이는 노드를 대상으로 하여 해당 노드에 대한 CSSOM 정보를 찾고 여기서 발견한 CSS 스타일 정보를 이 노드에 적용한다. DOM 노드에 CSS를 적용하는 과정은 크게 두 가지로 나뉜다.6-1. 레이아웃(layout) : 각 노드가 브라우저 화면의 어느 좌표에 정확히 나타나야 하는지 계산하는 과정. 레이아웃 과정을 거치면 반드시 페인팅 과정을 거친다.
6-2. 페인팅(painting) : 레이아웃 단계를 거친 노드에 색과 같은 실제 유효한 모습을 그리는 과정
리액트 파이버
는 리액트에서 관리하는 평범한 자바스크립트 객체다.
파이버는 파이버 재조정자(Fiber reconciler
)가 관리하는데, 이는 가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하며, 차이가 있으면 변경에 관련된 정보를 가지고 있는 파이버를 기준으로 화면에 렌더링을 요청하는 역할을 한다.리액트 파이버는 리액트 웹 애플리케이션에서 발생하는 애니메이션, 레이아웃, 사용자 인터랙션에 따른 올바른 결과물을 만드는 반응성 문제를 해결하기 위해 존재한다.
리액트 파이버는 다음과 같은 일을 수행할 수 있다.
1️⃣. 작업을 작은 단위로 분할하고 쪼갠 다음, 우선순위를 매김
2️⃣. 이러한 작업을 일시 중지하고 나중에 다시 시작할 수 있음.
3️⃣. 이전에 했던 작업을 다시 재사용하거나 필요하지 않은 경우에는 폐기할 수 있음.이 과정은 비동기로 일어난다.
파이버는 렌더 단계
와 커밋 단계
로 순서로 작업된다.
렌더 단계
커밋 단계
commitWork()
가 실행된다. 이 과정은 랜더 단계와 다르게 동기식으로 일어나며, 중단될 수 있음.리액트 요소는 렌더링이 발생할 때마다 새롭게 생성된다.
반면, 파이버는 가급적 재사용된다.
더 자세히 말하자면, 파이버는 컴포넌트가 최초로 마운트되는 시점에 생성되어 이후에는 가급적 재사용된다.
✅ 파이버 트리란?
workInProgress Tree
리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해
workInProgress Tree
를 현재 트리로 변경한다.
=== 이러한 기술을더블 버퍼링
이라고 칭한다.
리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기
로 이루어진다.
반면, 실제 브라우저 구조인 DOM에 반영하는 것은 동기적으로 일어나야하고
, 또한 처리하는 작업이 많기 때문에 화면에서 불완전하게 표시될 수 있는 가능성이 높다.
그렇기 때문에 메모리상에서 먼저 수행해서 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것이다.
가상 DOM에 먼저 적용 후, 최종적인 변경 사항을 브라우저 DOM에 한 번에 적용
가상 DOM이라는 표현은 오직 웹 어플리케이션에서만 통용되는 개념이다.
리액트 파이버는 리액트 네이티브처럼 브라우저가 아닌 환경에서도 사용할 수 있기 때문에 파이버와 가상 DOM은 동일한 개념이 아니다.
React Fiber !== Virture DOM
함수형 컴포넌트는 useEffect
를 사용하여 클래스형 컴포넌트의 생명주기 메서드인 componentDidMount
, componentDidUpdate
, componentWillUnmount
를 비슷하게 구현할 수 있다.
useEffect가 해당 생명주기 메서드와 같다는 뜻이 절대 아니다. 비슷하게 구현할 수 있다는 것이 중요하다.
useEffect
Hook은 컴포넌트의 state를 활용해 동기적
으로 부수 효과를 만드는 메커니즘이다.
또한 함수형 컴포넌트는 렌더링이 일어날 때마다 그 순간의 값인 props
와 state
를 기준으로 렌더링된다. props
와 state
가 변경되었다면, 다시 한 번 그 값을 기준으로 함수형 컴포넌트가 호출된다고 볼 수 있다.
반면 클래스형 컴포넌트는 시간에 흐름에 따라 변화하는 this
를 기준으로 렌더링이 일어난다.
프론트엔드에서 말하는 렌더링은 아마 통상적으로 브라우저 렌더링
를 의미한다.
하지만 리액트에서도 렌더링
과정이 있으며, 이는 브라우저 렌더링
과는 다른 과정이다.
리액트의 렌더링은 다음과 같다.
리액트 렌더링 : 브라우저가 렌더링에 필요한 DOM 트리를 만드는 과정
브라우저 렌더링 : HTML, CSS, Javascript 등 개발자가 작성한 문서가 브라우저에서 출력되는 일련의 과정
리액트에서의 렌더링을 좀 더 자세히 이야기해보면 다음과 같다.
리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 현재 자신들이 가지고 있는
props
와state
의 값을 기반으로 어떻게 UI를 구성하고, 이를 바탕으로 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 일련의 과정을 의미한다.
브라우저의 렌더링은 웹페이지의 주소를 입력받으면 해당 주소에 해당 HTML 문서를 서버로부터 다운로드한다. 다운로드가 완료되면 브라우저는 HTML 문서를 파싱하여 DOM을 생성하게 된다.
그렇다면, 리액트의 렌더링은 언제 발생할까?
1) 함수형 컴포넌트의
useState()
의 두 번째 배열 요소인setter
가 실행되는 경우 :useState
가 반환하는 배열의 두 번째 인수는 클래스형 컴포넌트의setState
와 동일하게state
를 업데이트하는 함수다. 해당 함수가 실행되면 렌더링이 일어난다.
2) 함수형 컴포넌트의useReducer()
의 두 번째 배열 요소인dispatch
가 실행되는 경우 :useReducer
도useState
와 동일하게 상태와 이 상태를 업데이트하는 함수를 배열로 제공한다. 이 두 번째 배열 요소를 실행하면 컴포넌트의 렌더링이 일어난다 (=== dispatch action이 발생하면 렌더링이 일어난다)
3) 컴포넌트의 key props가 변경되는 경우 : 리액트에서key
는 명시적으로 선언돼 있지 않더라도 모든 컴포넌트에서 사용할 수 있는 특수한 props다. 일반적으로 key는 배열에서 하위 컴포넌트를 선언할 때 사용된다.
4) props가 변경되는 경우 : 부모로부터 전달받는 값인 props가 달라지면 이를 사용하는 자식 컴포넌트에서도 변경이 필요하기 때문에 리렌더링이 발생한다.
5) 부모 컴포넌트가 렌더링될 경우 : 부모 컴포넌트가 리렌더링된다면 자식 컴포넌트도 무조건 리렌더링이 일어난다.
컴포넌트의 key props가 변경되는 경우에 대해 조금 더 자세히 알아보자.
리액트에서 배열에 key를 쓰지 않으면 콘솔에 아래 경고가 출력된다.
Warning: Each child in a list should have a unique "key" prop
에러가 출력되어도, 굳이 배열에 key값을 지정해주지 않고 렌더링해도 각 요소는 정상적으로 나오는 것을 확인해본 적이 있을것이다. 그럼에도 불구하고 해당 에러가 우리에게 알려주는 메세지는 무엇일까?
🔥 <에러의 이유>
리액트에서 key
는 리렌더링이 발생하는 동안 형제 요소들 사이에서 동일한 요소를 식별하는 값이다.
리액트 파이버 트리 구조를 보면, 해당 트리 구조에서 형제 컴포넌트를 구별하기 위해 각자 sibling
이라는 속성값을 사용했음을 확인할 수 있다.
동일한 자식 컴포넌트가 여러 개 있는 구조가 있다고 가정해보자.
아래글을 이해하기 위해서는 React Fiber에 대한 이해가 있어야 합니다.
리렌더링이 발생하면 current
트리와 workInProgress
트리 사이에서 어떠한 컴포넌트가 변경이 있었는지 구별해야 하는데, 이 두 트리 사이에서 같은 컴포넌트인지를 구별하는 값이 바로 key
이다.
key
를 가지고 있는 컴포넌트는 이를 기준으로 구별할 수 있지만, 만약 key가 없다면 단순하게 React fiber 내부의 sibling
인덱스만을 기준으로 판단하게 된다.
return
) 이 결과와 이전 가상 DOM을 비교하는 과정을 거쳐 변경이 필요한 컴포넌트를 체크하는 단계다.type, props, key
이다. 이 3개 중에서 하나라도 변경된 것이 있으면 변경이 필요한 컴포넌트로 체크해둔다.useLayoutEffect
Hook을 호출한다.⭐️ 중요한 사실
리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니다.
렌더링을 수행했으나 커밋 단계까지 갈 필요가 없다면,(===변경 사항을 계산했는데 아무런 변경 사항이 감지되지 않는다면)
이 커밋 단계는 생략될 수 있다.
렌더링 과정 중 첫 번째 단계인 렌더 단계 에서 변경 사항을 감지할 수 없다면, 커밋 단계가 생략되어 브라우저의 DOM 업데이트가 일어나지 않을 수 있다.
(브라우저 리렌더링이 일어나지 않는 경우)