Unuuuuu

렌더와 커밋은 무엇인가?

React
·

컴포넌트는 화면에 표시되기 전에 리액트에 의해 반드시 렌더링되어야 합니다.
이 프로세스를 이해하면 작성한 코드가 어떻게 실행될지 생각하고, 실행 동작을 설명하는 데 도움이 될 것입니다.

다음의 것들을 알게 됩니다.

  • 리액트에서 렌더링은 무엇을 의미하는지
  • 리액트는 언제 그리고 왜 컴포넌트를 렌더링하는지
  • 화면에 컴포넌트를 표시하는 단계
  • 렌더링은 왜 항상 DOM 업데이트를 발생시키지 않는지

컴포넌트를 요리라고 생각해 보겠습니다. 그리고 이때 리액트는 고객으로부터 주문받고 요리를 가져다주는 웨이터라고 생각해 보겠습니다. 이 프로세스는 다음의 세 단계를 가집니다.

  1. 렌더링을 트리거합니다. (주방으로 고객의 주문을 전달합니다.)
  2. 컴포넌트를 렌더링합니다. (주방에서 요리를 준비합니다.)
  3. DOM에 커밋합니다. (테이블에 요리를 놓습니다.)

단계 1: 렌더링을 트리거합니다.

컴포넌트는 다음의 두 가지 경우에 렌더링합니다.

  1. 컴포넌트의 초기에 렌더링합니다.
  2. 컴포넌트 자신이나 조상 컴포넌트 중 하나가 가지고 있는 state가 업데이트되었을 때 렌더링합니다.

초기 렌더링

앱이 시작되면 초기 렌더링을 트리거해야 합니다. 이것은 타겟 DOM 노드와 함께 createRoot 를 호출한 다음, 컴포넌트와 함께 render 메소드를 호출하는 것으로 수행됩니다.

import App from "./App.js";
import { createRoot } from "react-dom/client";

const root = createRoot(document.getElementById("root"));
root.render(<App />);

state 업데이트 시 발생하는 리렌더링

컴포넌트가 초기에 렌더링되고 나면, set 함수를 이용해 state를 업데이트하는 것으로 추가 렌더링을 트리거할 수 있습니다. 컴포넌트의 state를 업데이트하면 자동으로 렌더링이 대기열에 추가됩니다.

단계 2: 리액트가 컴포넌트를 렌더링합니다.

렌더링을 트리거한 이후에, 리액트는 무엇을 화면에 표시할지 알아내기 위해 컴포넌트를 호출합니다. “렌더링”은 리액트가 컴포넌트를 호출하는 것입니다.

  • 초기 렌더링에서 리액트는 루트 컴포넌트를 호출합니다.
  • 이후의 렌더링에서 리액트는 상태가 업데이트되어 렌더링을 트리거한 컴포넌트를 호출합니다.

이 프로세스는 반복적입니다. 업데이트된 컴포넌트가 어떤 다른 컴포넌트를 반환하고 있다면, 리액트는 그 컴포넌트를 다음으로 렌더링합니다. 그리고 그 컴포넌트가 또 어떤 다른 컴포넌트를 반환하고 있다면, 리액트는 그 컴포넌트를 다음으로 렌더링합니다. 이 프로세스는 더 이상 중첩된 컴포넌트가 없고, 리액트가 화면에 무엇이 표시되어야 하는지 정확히 알 때까지 계속됩니다.

다음의 예시에서 리액트는 Article 함수를 호출하고, Paragraph 함수를 여러 번 호출할 것입니다.

function Paragraph() {
  return (
    <p>
      Lorem ipsum is placeholder text commonly used in the graphic, print, and
      publishing industries for previewing layouts and visual mockups.
    </p>
  );
}

export default function Article() {
  return (
    <article>
      <h1>Title</h1>
      <Paragraph />
      <Paragraph />
      <Paragraph />
    </article>
  );
}

  • 초기 렌더링을 하는 동안, 리액트는 <article>, <h1>, 그리고 세 개의 <p> 태그에 대한 DOM 노드를 생성합니다.
  • 리렌더링을 하는 동안, 리액트는 이전 렌더링 이후 변경된 속성이 있다면 그것이 어떤 속성인지 계산합니다. 그런데, 다음 단계인 커밋 단계가 될 때까지 그 정보를 가지고 아무 일도 하지는 않습니다.

단계 3: 리액트가 DOM에 변경 사항을 커밋합니다.

컴포넌트를 렌더링(호출)한 이후에, 리액트는 DOM을 수정합니다.

  • 초기 렌더링에서 리액트는 appendChild DOM API를 사용하여 생성한 모든 DOM 노드를 화면에 표시합니다.
  • 리렌더링에서 리액트는 DOM이 최신 렌더링 결과와 일치하도록 렌더링 중에 계산한 최소한의 필수 작업을 적용합니다.

리액트는 렌더링 간에 차이가 있는 DOM 노드만을 변경합니다. 예를 들어, 매초 state가 업데이트되어 리렌더링하는 컴포넌트가 있습니다. <input> 에 어떤 텍스트를 추가하여 그것의 값을 업데이트할 수 있지만, 컴포넌트가 리렌더링될 때 그 텍스트가 사라지지 않는 것에 주목해 주세요.

import { useState, useEffect } from "react";

function useTime() {
  const [time, setTime] = useState(() => new Date());
  useEffect(() => {
    const id = setInterval(() => {
      setTime(new Date());
    }, 1000);
    return () => clearInterval(id);
  }, []);
  return time;
}

export default function Clock() {
  const time = useTime();
  return (
    <>
      <h1>{time.toLocaleTimeString()}</h1>
      <input />
    </>
  );
}

이 단계에서 리액트는 새로운 time 을 가지고 <h1> 의 내용만 업데이트하기 때문에 이처럼 동작합니다. <input> 이 JSX에서 지난번과 같은 위치에 나타나는 것을 확인하기 때문에, 리액트는 <input> 또는 그 값을 건드리지 않습니다.

에필로그: 브라우저가 패인트합니다.

렌더링이 끝난 이후에 리액트는 DOM을 업데이트하고, 브라우저는 화면을 리패인트합니다. 이 프로세스는 “브라우저 렌더링”이라고 합니다.

Resources