[리액트 공식문서 읽기] ADDING INTERACTIVITY - Render and Commit

JaeHong Jeong·2023년 9월 4일
post-thumbnail

Overview

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

컴포넌트가 주방에서 요리사가 되어 재료로 맛있는 요리를 만들고 있다고 상상해봐라. 이 시나리오에서 리액트는 고객의 요청을 접수하고 주문을 전달하는 웨이터이다. UI를 요청하고 제공하는 프로세스는 세 단계로 구성된다.

  1. 렌더링 트리거(손님이 주문한 음식을 주방으로 전달)
  2. 컴포넌트 렌더링(주방에서 주문 준비)
  3. DOM에 커밋(테이블에 주문하기)

Step 1: Trigger a render

컴포넌트가 렌더링되는 데는 두 가지 이유가 있다.

  1. 컴포넌트의 첫번째 렌더링
  2. 컴포넌트(또는 상위 항목 중 하나)의 상태가 업데이트되었을 때

Initial render

앱이 시작되면 초기 렌더링을 트리거해야한다. 프레임워크와 샌드박스는 때때로 이 코드를 숨기지만, 이는 대상 DOM 노드로 createRoot를 호출한 다음 컴포넌트로 render 메서드를 호출하여 수행된다.

// index.js

import Image from './Image.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />);
// 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"
    />
  );
}

root.render() 호출을 주석 처리하여 컴포넌트가 사라지는 것을 확인해라

Re-renders when state updates

컴포넌트가 처음 렌더링되면 set function를 사용하여 해당 상태를 업데이트하여 추가 렌더링을 트리거할 수 있다. 컴포넌트의 상태를 업데이트하면 렌더링이 자동으로 대기열에 추가된다.(식당 손님이 갈증이나 배고픈 상태에 따라 차, 디저트 등을 먼저 주문한 후 주문하는 모습을 상상해보아라.)

Step 2: React renders your components

렌더링을 트리거한 뒤 리액트는 컴포넌트를 호출하여 화면에 표시할 내용을 파악한다. “렌더링”은 리액트가 컴포넌트를 호출하는 것이다.

  • 첫번째 렌더링 시 리액트는 루트 컴포넌트를 호출한다.
  • 후속 렌더링의 경우 리액트는 상태 업데이트가 렌더링을 트리거한 함수 컴포넌트를 호출한다.

이 프로세스는 재귀적이다. 업데이트된 컴포넌트가 다른 컴포넌트를 반환하면 리액트는 해당 컴포넌트를 다음에 렌더링하고 해당 컴포넌트도 무언가를 반환하면 해당 컴포넌트를 다음에 렌더링하는 식이다. 더 이상 중첩된 컴포넌트가 없고 리액트가 화면에 표시해야할 내용을 정확히 알 때까지 프로세스가 계속된다.

예제에서 리액트는 Gallery()Image() 를 여러 번 호출한다.

// 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"
    />
  );
}
// index.js

import Gallery from './Gallery.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Gallery />);
  • 첫번째 렌더링 중에 리액트는 <section><h1>, 3개의 <img> 태그에 대한 DOM 노드를 생성한다.
  • 다시 렌더링하는 동안 리액트는 이전 렌더링 이 후 변경된 속성이 있는지 계산한다. 다음 단계인 커밋 단계까지 해당 정보로 아무 작업도 수행하지 않는다.
💡 Pitfall

렌더링은 항상 순수한 계산이어야 한다.

  • 동일한 입력, 동일한 출력. 동일한 입력이 주어지면 컴포넌트는 항상 동일한 JSX를 반환해야한다.(토마토 샐러드를 주문할 때 양파 샐러드를 받아서는 안된다.)
  • 자신의 일을 생각한다. 렌더링 전에 존재했던 객체나 변수를 변경해서는 안된다.(하나의 주문이 다른 사람의 주문을 변경해서는 안된다.)

그렇지 않으면 코드베이스가 복잡해지면서 혼란스러운 버그와 예측할 수 없는 동작이 발생할 수 있다. “Strict Mode”로 개발할 때 리액트는 각 컴포넌트를 두번 호출하므로 불순한 함수로 인한 실수를 표면화하는 데 도움이 될 수 있다.

💡 DEEO DIVE

성능 최적화

업데이트된 컴포넌트 내에 중첩된 모든 컴포넌트를 렌더링하는 기본 동작은 업데이트된 컴포넌트가 트리에서 매우 높은 경우 성능에 적합하지 않는다. 성능 문제가 발생한 경우 Performance에 설명된 문제 해결을 위한 여러 가지 성택 방법이 있다. 너무 일찍 최적화 하지 말아라.

Step 3: React commits changes to the DOM

컴포넌트를 렌더링(호출)한 뒤 리액트는 DOM을 수정한다.

  • 초기 렌더링의 경우, 리액트는 appendChild() DOM API를 사용하여 생성된 모드 DOM 노드를 화면에 배치한다.
  • 리렌더링 경우 리액트는 DOM이 최신 렌더링 출력과 일치하도록 하기위해 필요한 최소한 작업(렌더링 중에 계산됨)을 적용한다.

리액트는 렌더링 간에 차이가 있는 경우에만 DOM 노드를 변경한다. 예를 들어, 매초 부모로부터 전달된 다른 props로 다시 렌더링하는 컴포넌트가 있다. <input> 에 일부 텍스트를 추가하여 해당 value 를 업데이트할 수 있지만 컴포넌트가 다시 렌더링될 때 텍스트가 사라지지 않는 방법을 확인해라.

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

이는 마지막 단계에서 리액트가 <h1> 의 내용만 새로운 time으로 업데이트하기 때문에 작동한다. <input> 이 지난번과 같은 위치에 JSX에 나타나는 것을 확인하므로 리액트는 <input> 이나 그 value 를 건드리지 않는다.

Epilogue: Browser paint

렌더링이 완료되고 리액트가 DOM을 업데이트한 후 브라우저는 화면을 다시 페인팅한다. 이 프로세스를 “브라우저 렌더링”이라고 하지만 문서전체에서 혼동을 피하기 위해 “페인팅”이라고 부르겠다.

Recap

  • 리액트 앱의 모든 화면 업데이틑 세 단계로 이루어진다.
    1. Trigger
    2. Render
    3. Commit
  • Strict Mode를 사용하여 컴포넌트의 실수를 찾을 수 있다.
  • 렌더링 결과가 지난번과 같으면 리액트는 DOM을 건드리지 않는다.
profile
반갑습니다.

0개의 댓글