모던 React Deep Dive (ch 2)

·2024년 2월 23일
0

React Deep Dive

목록 보기
2/2
post-thumbnail

모던 리액트 Deep Dive -Chapter 2_리액트 핵심요소 깊게 살펴보기

JSX란?

  • XML과 유사한 내장형 구문. JSX를 꼭 리액트에서만 사용하라는 법은 없으니 기억해두자.
  • 자바스크립트 표준 코드가 아니다. 때문에 반드시 JSX는 반드시 트랜스파일러를 거쳐야 비로소 자바스크립트 런타임이 이해할 수 있는 의미 있는 자바스크립트 코드로 변환된다.

JSX 설계 목적?

다양한 트랜스파일러에서 다양한 속성을 가진 트리 구조를 토큰화하여 ECMAScript로 변환하는 데 초점을 두고 있음.

핵심
➡️ JSX는 자바스크립트 내부에서 표현하기 까다로웠던 XML 스타일의 트리 구문을 작성하는 데 많은 도움을 주는 문법이다.

JSX의 정의

  • 기본적으로 아래 4가지 컴포넌트를 기반으로 구성되어있다.

    JSXElement, JSXAttributes, JSXChildren, JSXStrings

  1. JSXElement
    1-1. JSX를 구성하는 가장 기본 요소. HTML의 요소(element)와 비슷한 역할을 함. 해당 컴포넌트의 조건은 다음과 같다.

    <JSXOpeningElement> | <JSXClosingElement> | <JSXSelfClosingElement> | <JSXFragment>

  2. JSXAttributes
    2-1. JSXElement부여할 수 있는 속성을 의미한다.

  3. JSXChildren
    3-1. JSXElement자식 값을 나타낸다. JSX는 속성을 가진 트리 구조를 나타내기 위해 만들어졌기 때문에 JSX로 부모와 자식 관계를 나타낼 수 있으며, 그 자식을 JSXChildren이라고 한다.

  4. 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을 다룰때, 무엇을 주의해야 하는가?

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) : 레이아웃 단계를 거친 노드에 색과 같은 실제 유효한 모습을 그리는 과정

가상 DOM을 위한 아키텍처, 리액트 파이버

리액트 파이버는 리액트에서 관리하는 평범한 자바스크립트 객체다.
파이버는 파이버 재조정자(Fiber reconciler)가 관리하는데, 이는 가상 DOM과 실제 DOM을 비교해 변경 사항을 수집하며, 차이가 있으면 변경에 관련된 정보를 가지고 있는 파이버를 기준으로 화면에 렌더링을 요청하는 역할을 한다.

리액트 파이버는 리액트 웹 애플리케이션에서 발생하는 애니메이션, 레이아웃, 사용자 인터랙션에 따른 올바른 결과물을 만드는 반응성 문제를 해결하기 위해 존재한다.

리액트 파이버는 다음과 같은 일을 수행할 수 있다.

1️⃣. 작업을 작은 단위로 분할하고 쪼갠 다음, 우선순위를 매김
2️⃣. 이러한 작업을 일시 중지하고 나중에 다시 시작할 수 있음.
3️⃣. 이전에 했던 작업을 다시 재사용하거나 필요하지 않은 경우에는 폐기할 수 있음.

이 과정은 비동기로 일어난다.

파이버는 렌더 단계커밋 단계로 순서로 작업된다.

  1. 렌더 단계
  • 렌더 단계에서 리액트는 사용자에게 노출되지 않는 모든 비동기 작업을 수행한다.
  • 해당 단계에서 파이버의 작업 및 우선순위 지정, 중지 및 버리기 작업이 일어난다.
  1. 커밋 단계
  • 커밋 단계에서는 DOM에 실제 변경 사항을 반영하기 위한 작업이 일어난다.
  • commitWork()가 실행된다. 이 과정은 랜더 단계와 다르게 동기식으로 일어나며, 중단될 수 있음.

리액트 요소와 파이버의 차이점

리액트 요소는 렌더링이 발생할 때마다 새롭게 생성된다.
반면, 파이버는 가급적 재사용된다.
더 자세히 말하자면, 파이버는 컴포넌트가 최초로 마운트되는 시점에 생성되어 이후에는 가급적 재사용된다.

리액트 파이버 트리

✅ 파이버 트리란?

  • 파이버 트리는 리액트 내부에서 두 개가 존재한다.
  1. 현재 모습을 담은 파이버 트리
  2. 작업 중인 상태를 나타내는 workInProgress Tree

    리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해 workInProgress Tree를 현재 트리로 변경한다.
    === 이러한 기술을 더블 버퍼링이라고 칭한다.

파이버와 가상 DOM

리액트 컴포넌트에 대한 정보를 1:1로 가지고 있는 것이 파이버이며, 이 파이버는 리액트 아키텍처 내부에서 비동기로 이루어진다.

반면, 실제 브라우저 구조인 DOM에 반영하는 것은 동기적으로 일어나야하고, 또한 처리하는 작업이 많기 때문에 화면에서 불완전하게 표시될 수 있는 가능성이 높다.

그렇기 때문에 메모리상에서 먼저 수행해서 최종적인 결과물만 실제 브라우저 DOM에 적용하는 것이다.

가상 DOM에 먼저 적용 후, 최종적인 변경 사항을 브라우저 DOM에 한 번에 적용

알아둬야 할 것

가상 DOM이라는 표현은 오직 웹 어플리케이션에서만 통용되는 개념이다.
리액트 파이버는 리액트 네이티브처럼 브라우저가 아닌 환경에서도 사용할 수 있기 때문에 파이버와 가상 DOM은 동일한 개념이 아니다.
React Fiber !== Virture DOM


함수형 컴포넌트

함수형 컴포넌트useEffect를 사용하여 클래스형 컴포넌트의 생명주기 메서드인 componentDidMount, componentDidUpdate, componentWillUnmount를 비슷하게 구현할 수 있다.

useEffect가 해당 생명주기 메서드와 같다는 뜻이 절대 아니다. 비슷하게 구현할 수 있다는 것이 중요하다.

useEffect Hook은 컴포넌트의 state를 활용해 동기적으로 부수 효과를 만드는 메커니즘이다.

또한 함수형 컴포넌트는 렌더링이 일어날 때마다 그 순간의 값인 propsstate를 기준으로 렌더링된다. propsstate가 변경되었다면, 다시 한 번 그 값을 기준으로 함수형 컴포넌트가 호출된다고 볼 수 있다.

반면 클래스형 컴포넌트는 시간에 흐름에 따라 변화하는 this를 기준으로 렌더링이 일어난다.


⭐️ 리액트 렌더링은 어떻게 일어나는가? ⭐️

프론트엔드에서 말하는 렌더링은 아마 통상적으로 브라우저 렌더링를 의미한다.
하지만 리액트에서도 렌더링 과정이 있으며, 이는 브라우저 렌더링과는 다른 과정이다.

리액트의 렌더링은 다음과 같다.

리액트 렌더링 : 브라우저가 렌더링에 필요한 DOM 트리를 만드는 과정
브라우저 렌더링 : HTML, CSS, Javascript 등 개발자가 작성한 문서가 브라우저에서 출력되는 일련의 과정

리액트에서의 렌더링을 좀 더 자세히 이야기해보면 다음과 같다.

리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 현재 자신들이 가지고 있는 propsstate의 값을 기반으로 어떻게 UI를 구성하고, 이를 바탕으로 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 일련의 과정을 의미한다.

리액트의 렌더링이 일어나는 이유?

브라우저의 렌더링은 웹페이지의 주소를 입력받으면 해당 주소에 해당 HTML 문서를 서버로부터 다운로드한다. 다운로드가 완료되면 브라우저는 HTML 문서를 파싱하여 DOM을 생성하게 된다.

그렇다면, 리액트의 렌더링은 언제 발생할까?

  1. 최초렌더링
  • 사용자가 처음 웹 애플리케이션에 진입하면 당연히 렌더링해야 할 결과물이 필요함. 리액트는 브라우저에 이 정보를 제공하기 위해 최초 렌더링을 수행한다.
  1. 리렌더링
  • 리렌더링은 처음 웹 애플리케이션에 진입했을 때, 최초 렌더링이 발생한 이후로 발생하는 모든 렌더링을 의미한다.
    아래는 리렌더링이 발생하는 경우를 나열한 것이다. (함수형 컴포넌트 기준)

    1) 함수형 컴포넌트의 useState()의 두 번째 배열 요소인 setter가 실행되는 경우 : useState가 반환하는 배열의 두 번째 인수는 클래스형 컴포넌트의 setState와 동일하게 state를 업데이트하는 함수다. 해당 함수가 실행되면 렌더링이 일어난다.
    2) 함수형 컴포넌트의 useReducer()의 두 번째 배열 요소인 dispatch가 실행되는 경우 : useReduceruseState와 동일하게 상태와 이 상태를 업데이트하는 함수를 배열로 제공한다. 이 두 번째 배열 요소를 실행하면 컴포넌트의 렌더링이 일어난다 (=== 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 인덱스만을 기준으로 판단하게 된다.

리액트 렌더링은 '렌더'와 '커밋'으로 분리되어 실행된다.

  1. 렌더 단계
  • 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업을 의미한다.
  • 렌더링 프로세스에서 컴포넌트를 실행해(return) 이 결과와 이전 가상 DOM을 비교하는 과정을 거쳐 변경이 필요한 컴포넌트를 체크하는 단계다.
  • 렌더 단계에서 비교하는 것은 type, props, key이다. 이 3개 중에서 하나라도 변경된 것이 있으면 변경이 필요한 컴포넌트로 체크해둔다.
  1. 커밋 단계
  • 렌더 단계의 변경 사항을 실제 DOM에 적용해 사용자에게 보여주는 과정을 의미한다. 해당 단계가 끝나야 비로소 리액트 렌더링이 끝나고, 브라우저의 렌더링이 발생한다.
  • 리액트가 먼저 DOM을 커밋 단계에서 업데이트한다면, 이렇게 만들어진 모든 DOM 노드 및 인스턴스를 가리키도록 리액트 내부의 참조를 업데이트한다.
    그 다음, 함수형 컴포넌트에서는 useLayoutEffect Hook을 호출한다.

⭐️ 중요한 사실

리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니다.
렌더링을 수행했으나 커밋 단계까지 갈 필요가 없다면,(===변경 사항을 계산했는데 아무런 변경 사항이 감지되지 않는다면)이 커밋 단계는 생략될 수 있다.


렌더링 과정 중 첫 번째 단계인 렌더 단계 에서 변경 사항을 감지할 수 없다면, 커밋 단계가 생략되어 브라우저의 DOM 업데이트가 일어나지 않을 수 있다.
(브라우저 리렌더링이 일어나지 않는 경우)

profile
- 배움에는 끝이 없다.

0개의 댓글