먼저 말하자면, 리액터에서의 렌더링은 일반적으로 알고있는 '브라우저의 렌더링'과는 조금 다르다.
리액트에서 UI를 요청하고 제공하는 과정은 3단계로 이루어진다. 이를 식당으로 비유해보자면
렌더링을 트리거하는 요인은 두 가지가 있다.
앱이 실행되었을 때 트리거 된다. 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
는 전달받은 컴포넌트를 보여주는(표시하는) 역할을 한다.
첫 렌더링 이후 부터는, useState
의 set~
함수를 이용해 상태를 다른 값으로 업데이트하고 렌더링을 추가적으로 트리거할 수 있다. 상태를 업데이트 한다는 것은 렌더링을 자동으로 큐에 넣는다.
즉, 앱을 시작하거나 컴포넌트의 상태가 업데이트 되었을 때에 리액트에게 렌더링을 하라고 전달한다.
렌더링이 "트리거" 된 후에, 리액트는 컴포넌트를 호출해서 화면에 출력할 내용을 파악한다. "렌더링이란, 리액트가 컴포넌트를 호출하는 것"이다.
렌더링: 리액트가 컴포넌트를 호출하는 것
이 과정은 반복적인 작업이다. 만약 컴포넌트가 다른 컴포넌트를 반환하고, 그 컴포넌트도 다른 컴포넌트를 반환하고... 이런식의 반복으로 계속해서 호출이 발생할 수 있다.
다음의 예시를 보자
// 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개이다.)의 돔 노드를 생성한다.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