렌더링이란 리액트가 컴포넌트를 호출하는 것을 말한다. 실제로 화면(브라우저)에 그려지는 것 또한 렌더링이라고 하기 때문에 용어의 혼동이 생길 수 있는데, 이 글에서 후자는 브라우저 렌더링이라고 명시하여 작성했다. 공식 문서에서는 다음 세 단계에 따라 화면 업데이트가 이루어진다고 한다. 그리고, 그 과정을 괄호와 같이 비유한다.
렌더링은 크게 초기 렌더링이나 state 업데이트 시에 트리거된다.
렌더링 과정에서 초기 렌더링 시에는 루트 컴포넌트를 호출하고, state 업데이트 시에는 해당 컴포넌트를 호출한다. 이때, 컴포넌트를 호출하여 곧바로 실제 DOM을 수정하지 않고 가상 DOM 트리를 구성한다.
초기 렌더링 시에는 appendChild()라는 DOM API를 사용해 생성된 모든 DOM 노드를 화면에 표시, 즉 실제 DOM에 반영한다. state 업데이트 시에는 변경 사항을 계산하여 변경된 노드만 수정한다. 아래 예시에서 현재 시각이 time에 매초 전달될 때, time을 포함하는 h1은 수정되지만 그렇지 않은 input은 수정되지 않는 것이다.
export default function Clock({ time }) {
return (
<>
<h1>{time}</h1>
<input />
</>
);
}
브라우저 렌더링은 리액트가 실제 DOM을 수정하고 난 후 브라우저가 화면을 다시 그리는 과정이다. 공식 문서에서는 이를 painting이라 부르고 있다.
이렇게만 정리하면 의문이 생기는 지점이 있다. 만약 아래와 같이 하나의 이벤트 핸들러에서 여러 개의 state를 업데이트하면 어떻게 될까? 각각의 state 업데이트가 렌더링을 트리거하면 총 2번의 리렌더링이 일어날까?
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
const handleClick = () => {
setCount(count + 1);
setText("Updated!");
console.log(count);
};
return (
<div>
<p>Count: {count}</p>
<p>Text: {text}</p>
<button onClick={handleClick}>Update</button>
</div>
);
}
아니면, 아래처럼 하나의 state를 여러 번 수정하면 어떻게 될까? (사실 이런 코드를 작성할 일은 많지 않겠지만...) set함수 호출에 따라 세 번의 렌더링이 일어나는 걸까? 결국 변경된 state는 number 하나이니 한 번의 렌더링이 일어나는 걸까?
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 2);
setNumber(number + 3);
}}>+3</button>
경험적으로 콘솔에 count가 한 번만 출력되고, 가장 마지막 setNumber에 의한 값으로 설정된다는 것을 우리는 안다. 이것은 리액트가 배치 업데이트(batched updates)로 렌더링을 최적화하기 때문이다.
즉, 일괄적으로 업데이트한다는 것이다. 리액트는 어떤 이벤트 핸들러에서, 또는 어떤 useEffect 내부에서 일어나는 상태 변화들을 한꺼번에 처리한다. 그래서 나는 아래와 같이 0번 순서를 집어넣어 생각하고 있다. 이것저것 자료들을 찾아보고 생각하면서 도출해 낸 결론이라 오류가 있을 수도!!............
상태 계산 단계에서는 업데이트되는 상태들을 일괄적으로 계산한다. 다시 위의 counter 예제에서, 버튼을 클릭했을 때 count와 text가 업데이트된다는 것을 먼저 계산한 후, 계산된 모든 상태에 대한 컴포넌트를 한번에 호출한다.
setNumber를 여러 번 호출하는 예제에서도 마찬가지로 우선 이벤트 핸들러 전체를 실행할 것이다. number의 초기값이 0이었을 경우, number는 1이 되었다가, 2가 되었다가, 3이 되고, 결과적으로 number가 업데이트되었으니 관련 컴포넌트를 호출한다.
이때 setNumber에서 사용되는 number에는 모두 기존 number의 값인 0이 들어가는데, 자세한 내용은 다음 챕터 스냅샷으로서의 state에서 다룬다. 다음 포스팅에서 다뤄보겠다.