[리액트] 리액트의 렌더와 커밋

도현수·2023년 5월 17일
0

React

목록 보기
6/6

먼저 말하자면, 리액터에서의 렌더링은 일반적으로 알고있는 '브라우저의 렌더링'과는 조금 다르다.

리액트에서 UI를 요청하고 제공하는 과정은 3단계로 이루어진다. 이를 식당으로 비유해보자면

  1. 렌더링의 트리거 - 주방에 주문을 알리기
  2. 컴포넌트의 렌더링 - 요리하기
  3. DOM에 커밋하기 - 테이블에 음식을 가져다주기

1. 렌더링의 "트리거"

렌더링을 트리거하는 요인은 두 가지가 있다.

  1. 컴포넌트의 첫번째 렌더링
  2. 컴포넌트(혹은 조상 컴포넌트 중 하나)의 상태 업데이트

첫번째 렌더링

앱이 실행되었을 때 트리거 된다. createRoot에 타겟 DOM 노드를 인자로 전달한 후에, 이것의 render 메서드를 호출한다. 이는 리액트 프로젝트의 index.js에서 확인해 볼 수 있는 사항이다.

// root라는 id를 가진 DOM노드를 createRoot에 인자로 전달. createRoot와 ReactDOM은 같은 역할을 한다.
const root = ReactDOM.createRoot(document.getElementById("root"));
// root의 render 메서드를 실행. 렌더링 하고 싶은 컴포넌트 (=App 컴포넌트)를 인자로 전달
root.render(<App />);
//image.js
export default function Image() {
  return (
    <img
      src="https://i.imgur.com/ZF6s192.jpg"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}

이처럼 프로젝트 전체가 리액트로 되어있다면 보통 단 한번의 createRoot 메서드만 사용한다. 즉, root 라는 id를 가진 div 태그를 전달해 이를 DOM의 root로 만들고 리액트가 이를 관리하게 된다(그래서 div의 id가 root인가 싶다.). 이후 root.render 는 전달받은 컴포넌트를 보여주는(표시하는) 역할을 한다.

상태가 업데이터 되었을때의 리렌더링

첫 렌더링 이후 부터는, useStateset~ 함수를 이용해 상태를 다른 값으로 업데이트하고 렌더링을 추가적으로 트리거할 수 있다. 상태를 업데이트 한다는 것은 렌더링을 자동으로 큐에 넣는다.


즉, 앱을 시작하거나 컴포넌트의 상태가 업데이트 되었을 때에 리액트에게 렌더링을 하라고 전달한다.


2. 리액트에 의한 컴포넌트 렌더링

렌더링이 "트리거" 된 후에, 리액트는 컴포넌트를 호출해서 화면에 출력할 내용을 파악한다. "렌더링이란, 리액트가 컴포넌트를 호출하는 것"이다.

렌더링: 리액트가 컴포넌트를 호출하는 것

  • 첫번째 렌더링: 리액트는 루트 컴포넌트를 호출
  • 이후의 렌더링: 렌더링을 트리거하는 상태 업데이트가 발생한 함수 컴포넌트를 호출

이 과정은 반복적인 작업이다. 만약 컴포넌트가 다른 컴포넌트를 반환하고, 그 컴포넌트도 다른 컴포넌트를 반환하고... 이런식의 반복으로 계속해서 호출이 발생할 수 있다.

다음의 예시를 보자

// Gallery.js
export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image />
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return (
    <img
      src="https://i.imgur.com/ZF6s192.jpg"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}
import Gallery from './Gallery.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Gallery />);
  • 첫번째 렌더링: <section>, <h1>, <img> 태그들 (img 태그는 3개이다.)의 돔 노드를 생성한다.
  • 리렌더링: 이전 렌더링 이후에 변경된 프로퍼티가 있는지 확인한다. 하지만 커밋 단계가 될 때까지 이 정보들로 아무것도 하지 않는다.

3. DOM에 변경사항을 커밋하기

commit = 위탁하다, 맡기다.
즉 DOM에게 변경사항들을 넘긴다는(반영한다는) 말이다.

  • 첫번째 렌더링: 리액트가 appendChild() DOM API를 사용해서, 이 전 단계에서 만들어진 모든 DOM 노드들을 화면에 보여준다.

  • 리렌더링: 리액트는 필요한 최소한의 연산을 적용해서 DOM이 최신 렌더링 출력과 일치하도록 한다.

단, 여기서 리액트는 렌더링 간 차이가 있는 DOM 노드만을 바꾼다. 아래의 예시에서 <h1> 태그는 부모로부터 매 초마다 다른 props를 전달받아 리렌더링 하지만, <input> 에 텍스트를 입력해보면 <h1> 의 시간은 계속해서 업데이트 되지만, 텍스트는 사라지지 않는 것을 볼 수 있다.

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}

여기서 업데이트가 이루어지지 않는 것이지, 렌더링(호출)이 되지 않는것이 아니라는 점에 주의하자. 만약 input 요소를 컴포넌트로 만들고, 해당 컴포넌트가 '안녕하세요~' 라고 콘솔에 출력하게끔 만들면 1초마다 안녕하세요~ 라고 출력된다. 매 초마다 다시 렌더링 되는 것이다.

추가로... 브라우저 페인트

이렇게 렌더링이 완료되고 React가 DOM을 업데이트 하고 나면, 브라우저는 화면을 다시 paint 한다. 이 과정이 "브라우저 렌더링"으로 알려져 있지만, 리액트에서는 혼란을 피하기 위해 이를 "페인팅" 이라고 부른다.

즉, 우리가 알고있는 렌더링 = 페인트 라고 생각하자.

레퍼런스

https://react.dev/learn/render-and-commit#step-2-react-renders-your-components
https://react.dev/reference/react-dom/client/createRoot#ive-created-a-root-but-nothing-is-displayed

0개의 댓글