react rendering은 어떻게 일어나는가

jeongwon yun·2023년 3월 23일
0

react

목록 보기
1/1
post-thumbnail

rendering이란 무엇인가

props, state 를 통해 UI를 어떻게 구성할지 컴포넌트에게 요청하는 작업

rendering 프로세스 살펴보기

루트 컴포넌트에서 시작해서 업데이트가 필요하다고 플래그가 지정된 모든 컴포넌트를 찾는다.

지정되었다면 FunctionComponent()를 호출하고 렌더링 결과를 저장한다.

// 일반적인 jsx문법
return <SomeComponent a={42} b="testing">Text here</SomeComponent>

// 이것을 호출해서 변환된다.
return React.createElement(SomeComponent, {a: 42, b: "testing"}, "Text Here")

// 호출결과 element를 나타내는 객체로 변환된다.
{type: SomeComponent, props: {a: 42, b: "testing"}, children: ["Text Here"]}

컴포넌트 반환값은 React.createELement()를 호출 후 반환한다.

createElement는 UI 구조를 설명하는 React Element를 리턴한다.

모든 컴포넌트에서 이런 렌더링 반환값을 수집한다.

react는 DOM을 가상DOM과 비교하며 변경사항을 수집한다. (이를 reconciliation라고 한다.)

모든 변경사항을 하나의 동기 시퀀스로 DOM에 적용한다.

렌더와 커밋 단계

Render phase: 컴포넌트를 렌더링하고 변경사항을 계산하는 모든 작업
Commit phase: DOM에 변경사항을 적용하는 과정

리액트가 DOM을 업데이트한 후,

요청된 DOM 노드 및 컴포넌트 인스턴스를 가리키도록 모든 참조를 업데이트한다.

그 다음,

useLayoutEffect 훅을 호출한다.

리액트는 짧은 timeout를 세팅한 후, 만료되면 useEffect를 호출한다.

이를 Passive Effects라고 한다.

이번에 리액트 18에서 나온 Concurrent Mode의 경우, 브라우저가 이벤트를 처리할 수 있도록 렌더링 단계에서 작업을 일시 중지 할 수 있다.
리액트는 해당 작업을 나중에 다시 시작하거나, 버리거나, 다시 계산할 수 있다.
렌더링이 패스가 된 이후에도, 리액트는 커밋단계를 한단계 동기적으로 실행한다.

렌더링은 DOM을 업데이트하는 것이 아니고

어떤 가시적인 변경 없이도 렌더링 될 수 있다는 것이다.

react가 컴포넌트를 렌더링하는 경우

  • 컴포넌트는 이전과 같은 렌더링 결과물을 리턴해서 아무 변화가 일어나지 않을 수 있다.

  • Concurrent Mode에서 react는 컴포넌트 렌더링 작업을 여러번 할 수 있지만,
    다른 업데이트로 인해 현재 작업이 무효화되면 매번 렌더링 결과물을 버린다.


리액트는 어떻게 렌더링을 다루는가

rendering 순서를 만드는 법

최초 렌더링 이후, react가 re-rendering을 queueing 하는 방법은 여려가지가 있다.

  • useState의 setter

  • useReducer의 dispatch

  • ReactDOM.render를 호출하는 것 (react 18 deprecated)

일반적인 rendering 동작

react의 기본적인 동작은 부모 컴포넌트가 렌더링되면, 모든 자식 컴포넌트를 순차적으로 re-rendering한다.

Ex.

A > B > C > D 순서로 컴포넌트 트리가 있다고 가정하자.

B에 카운터를 올리는 버튼이 있고 이를 클릭했다.

  1. BsetState가 호출되어 B의 re-rendering이 렌더링 큐로 들어간다.

  2. react는 트리 최상단부터 렌더링 패스를 시작한다.

  3. A는 업데이트가 필요하다는 플래그가 없으므로 지나친다.

  4. B는 업데이트가 필요하다는 플래그가 있으므로 B를 re-rendering한다.
    BC를 리턴한다.

  5. C는 업데이트가 필요하다는 플래그가 없었다.
    그러나 부모인 B가 re-rendering했으므로 react는 하위 컴포넌트인 C를 re-rendering한다.
    CD를 리턴한다.

  6. D도 마찬가지로, 업데이트 플래그가 없지만, 상위 컴포넌트가 re-rendering했으므로 re-rendering한다.

즉,

렌더링은 하위에 있는 모든 컴포넌트를 렌더링하는 것이다.

props가 변경되었는지 신경쓰지 않는다.

상위 컴포넌트가 rendering하면, 하위 컴포넌트도 무조건 re-rendering 된다.

즉,

루트에서 setState를 호출한다는 것은, 컴포넌트 트리에 있는 모든 컴포넌트를 re-rendering 한다는 것을 의미한다.

리액트 rendering 규칙

rendering은 순수해야하고 부수작용이 없어야한다.

prop가 변경되는 것이 부수효과이고 이는 무언가를 망가뜨릴 수 있다.

ajax 호출도 부수효과를 일으키고, 예기치 못한 결과를 만들 수 있다.

rendering 로직이 할 수 없는 것

  • 존재하는 변수나 객체를 변경해서는 안된다.

  • Math.random() Date.now()와 같은 랜덤값을 생성할 수 없다.

  • 네트워크 요청을 할 수 없다.

  • state 업데이트

rendering 로직이 할 수 있는 것

  • 렌더링 도중에 새롭게 만들어진 객체를 변경

  • 에러 던지기

  • 아직 만들어지지 않은 데이터를 lazy 초기화하는 일(ex. 캐시)

컴포넌트 metadata와 fiber

react는 앱에 존재하는 모든 현재 컴포넌트 인스턴스를 추적하는 내부 데이터 구조를 가지고 있다.

이 데이터 구조의 핵심 부분은 아래와 같은 metadata 필드를 포함하고 있는 fiber라는 객체이다.

  • 컴포넌트 트리의 특정 시점에서 rendering해야하는 컴포넌트 타입의 유형

  • 이 컴포넌트와 관련된 prop, state의 상태

  • 부모, 형제, 자식 컴포넌트에 대한 포인터

  • react가 rendering 프로세스를 추적하는데 사용되는 기타 metadata

rendering 패스 동안, react는 fiber 객체의 트리를 순회하고

새로운 rendering 결과를 계산한 결과인 업데이트된 트리를 생성한다.

컴포넌트를 rendering하면 fiber에 연결 후, linkedList를 가져오고

다른 훅을 호출할 때마다 훅에 저장된 적절한 값을 반환한다.

컴포넌트 타입과 재조정 (Reconciliation)

react는 기존 컴포넌트 트리와 DOM 구조를 가능한 많이 재사용하면서 re-rendering 효율성을 추구한다.

동일한 유형의 컴포넌트 or HTML 노드를 트리의 동일한 위치에 rendering하도록 요청하면, react는 해당 컴포넌트 or HTML 노드를 만드는 대신 해당 업데이트만 적용한다.

즉, react에 대한 컴포넌트 타입을 같은 위치에 rendering하도록 요청이 있으면, 컴포넌트 인스턴스를 유지한다.

react rendering 로직은 elements의 type필드를 ===를 통해 먼저 비교한다.

element가

key와 Reconciliation

rendering 배치와 타이밍

렌더 동작의 엣지 케이스


rendering 성능 향상시키기


컨텍스트(Context)와 rendering 동작


요약


Context API, 상태관리 언제 써야 할까


references
https://yceffort.kr/2022/04/deep-dive-in-react-rendering#%EB%A0%8C%EB%8D%94%EB%A7%81%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80

0개의 댓글