[React] 리액트에서 렌더링이란?

상현·2024년 6월 13일
1

React

목록 보기
23/24
post-thumbnail

브라우저에서의 렌더링이란 HTML과 CSS를 기반으로 웹페이지에 필요한 UI를 그리는 과정을 말한다. 사용자에게 보여주는 정보를 그리는 과정인 만큼 무엇보다 중요하며, 성능에도 큰 영향을 미친다.

리액트의 렌더링은 브라우저가 렌더링에 필요한 DOM 트리를 만드는 과정을 말한다. 이 과정은 시간과 리소스를 소비해 수행되는 과정으로, 시간이 길어지고 복잡해질수록 UX를 저해하기 때문에 렌더링 과정에 대해 제대로 이해하고 주의하여 개발하는 것이 중요하다.

리액트에서의 렌더링

렌더링은 브라우저에서도 사용되는 용어이므로 리액트에서의 렌더링과 브라우저의 렌더링 두 가지를 혼동해서 사용하면 안된다.

리액트에서의 렌더링은 리액트로 만든 어플리케이션 트리안에 있는 모든 컴포넌트들이 각자 가지고 있는 props와 state를 기반으로 어떻게 DOM 결과를 브라우저에 제공할 것인지 계산하는 과정을 의미한다.

만약 컴포넌트가 props와 state와 같은 상태값을 가지고 있지 않다면 오직 해당 컴포넌트가 반환하는 JSX에 기반해 렌더링이 일어난다.

리액트에서 렌더링이 발생하는 경우

리액트에서 렌더링이 발생하는 시나리오는 다음과 같다.

  1. 최초 렌더링
  2. 리렌더링: 최초 렌더링 이후 발생하는 모든 렌더링

리렌더링이 발생하는 경우는 다음과 같다.

  • 클래스 컴포넌트의 setState가 실행되는 경우: state의 변화는 컴포넌트 상태의 변화를 의미하므로 리렌더링이 발생한다.

  • 클래스 컴포넌트의 forceUpdate가 실행되는 경우: 화면에 보여주는 값이 props나 state에 의존하고 있지 않아 자동으로 리렌더링이 일어나지 않는 경우 강제로 리렌더링을 발생시킬 수 있다.

  • 함수 컴포넌트의 useState의 setter가 실행되는 경우: 마찬가지로 state를 업데이트 하기 때문에 리렌더링이 발생한다.

  • 함수 컴포넌트의 useReducer의 dispatch가 실행되는 경우

  • 컴포넌트의 key props가 변경되는 경우: key는 명시적으로 선언돼 있지 않더라도 모든 컴포넌트에서 사용할 수 있는 특수한 props로 일반적으로 배열에서 컴포넌트를 렌더링할 때 사용된다.

    왜 key가 필요할까?
    리액트에서 key는 리렌더링이 발생할 때 형제 요소들 사이에서 동일한 요소를 식별하기 위한 값이다. 리액트 파이버에서, 리렌더링이 발생할 때 current 트리와 workInProgress 트리에서 어떤 컴포넌트가 변경됐는지를 구별해야 하는데, 이를 위한 값이 key다. key가 없다면 단순히 index만을 기준으로 판단하게 된다.

  • props가 변경되는 경우: 전달받은 props가 달라지면 자식 컴포넌트도 리렌더링이 일어난다.

  • 부모 컴포넌트가 리렌더링될 경우

리액트의 렌더링 프로세스

렌더링 프로세스가 시작되면 리액트는 루트에서부터 아래로 내려가면서 업데이트가 필요하다고 지정된 모든 컴포넌트를 찾는다. 업데이트가 필요한 컴포넌트가 발견되면, 클래스 컴포넌트는 render() 함수를, 함수 컴포넌트는 그 자체를 호출한 뒤 결과를 저장한다. 그 결과란 다음과 같다.

예를 들어, 다음과 같은 컴포넌트가 있다고 해보자.

const Main() {
  return (
    <TestComponent a={35} b="test">
      테스트 입니다.
    </TestComponent>
  )
}

위의 JSX 문법은 React.createElement를 호출해서 변환된다.

const Main() {
  return React.createElement(
    TestComponent,
    { a: 35, b: 'test' },
    '테스트 입니다.'
  )
}

최종 결과물은 다음과 같다.

{type: TestComponent, props: {a: 35, b: 'test', '테스트 입니다.'}}

이렇게 계산하는 과정을 재조정(Reconcilation)이라고 한다. 이러한 재조정 과정이 끝나면 모든 변경 사항을 하나의 동기 시퀀스로 DOM에 적용한다.

한 가지 주목할 점은, 리액트의 렌더링은 렌더 단계와 커밋 단계로 분리되어 실행된다는 것이다.

렌더와 커밋

렌더 단계는 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업을 말한다. 즉, 렌더링 프로세스에서 컴포넌트를 실행한 결과와 이전 가상 DOM을 비교하는 과정을 거쳐 변경이 필요한 컴포넌트를 체크하는 단계다.

비교하는 것은 크게 type, props, key 세 가지이다. 이 세 가지 중 하나라도 변경된 것이 있으면 변경이 필요한 컴포넌트로 체크한다.

커밋 단계는 렌더 단계에서의 변경 사항을 실제 DOM에 적용해 사용자에게 보여주는 과정을 말한다. 업데이트 되면, 모든 DOM 노드 및 인스턴스를 가리키도록 리액트 내부의 참조를 업데이트한다. 다음으로, 생명주기 개념의 메서드들을 호출한다. 클래스 컴포넌트는 componentDidMount, componentDidUpdate를 호출하고, 함수 컴포넌트는 useLayoutEffect를 호출한다.

여기서 알 수 있는 중요한 사실은 리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니라는 것이다.
렌더링을 수행했으나, 커밋 단계까지 갈 필요가 없다면 생략될 수 있다. 즉, 리액트의 렌더링은 꼭 가시적인 변경이 일어나지 않아도 발생할 수 있다.

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram

이 두 가지 과정으로 리액트의 렌더링은 항상 동기식으로 작동했다. 순서가 보장되지 않는 비동기식으로 이뤄질 경우 사용자는 하나의 상태에 대해 여러 가지 다른 UI를 보게 될 수 있기 때문이다.

그럼에도 비동기 렌더링 시나리오가 더 좋을 수 있는 경우가 있다. 예를 들어, A 컴포넌트의 렌더링 작업이 오래걸려 상대적으로 더 빠르게 렌더링할 수 있는 B컴포넌트라도 렌더링해서 보여줄 수 있을 것이다.
이렇게 의도된 우선순위로 컴포넌트를 비동기 렌더링, 이른바 동시성 렌더링이 리액트 18에 도입됐다.


이렇게 리액트에서 렌더링이 일어나는 경우와, 어떤 프로세스를 거쳐 DOM에 반영되는지 살펴봤다. 렌더링은 많은 비용이 드는 작업인 만큼, 리액트의 렌더링 시나리오에 대해 생각하면서 코드를 짜는 것이 좋겠다고 느꼈다.

참조: 모던 리액트 Deep Dive

profile
프론트엔드 개발자 🧑🏻‍💻 https://until.blog/@love

0개의 댓글