-
React 딥다이브프론트엔드 2025. 10. 16. 01:03
🤔 렌더링이란?
- 리액트에서 렌더링(Rendering)이란, 컴포넌트 현재의 props와 state를 기반으로 어떤 UI를 보여줄지 계산하여(가상 DOM 생성), 그 결과를 비교(diff)하고 실제 브라우저 DOM에 반영(commit)하는 전체 과정을 말한다.
- 만약 컴포넌트의 props, state가 없다면 컴포넌트가 반환하는 JSX값에 기반해 렌더링이 일어나게 된다.
리액트에서 렌더링이 일어나는 이유
- 최초 렌더링 : 사용자가 처음 애플리케이션에 접근하면 리액트는 브라우저에 정보를 제공하기 위해 최초 렌더링을 수행한다.
- 리렌더링 : 최초 렌더링이 발생한 이후 발생하는 모든 렌더링을 말한다.
- 클래스 컴포넌트 setState가 실행되는 경우 : setState 호출을 통해 클래스 컴포넌트에서는 state 변화를 인식하므로 리렌더링이 발생한다.
- 함수 컴포넌트의 useState()의 두번째 요소은 setter가 실행되는 경우 : useState가 반환하는 배열의 두번째 인수 또한 setState를 업데이트 하는 함수다. 이 함수가 실행되면 렌더링이 일어난다.
- 함수 컴포넌트의 useReducer의 두번째 요소 dispatch가 실행되는 경우 : useReducer도 useState와 마찬가지로 상태와 이 상태를 업데이트하는 함수를 배열로 제공한다. 두 번째 배열요소를 실행하면 컴포넌트의 렌더링이 일어난다
- 컴포넌트의 key props가 변경되는 경우 : 리액트에서 key는 명시적으로 선언되어 있지 않더라도 모든 컴포넌트에서 사용할 수 있는 특수한 props다. key는 배열에서 하위 컴포넌트를 선언할 때 사용된다.
- props가 변경되는 경우 : 부모로 부터 전달받은 props가 달라지면 이를 사용하는 자식컴포넌트에서도 변경이 필요해 리렌더링이 발생한다
- 부모 컴포넌트가 렌더링 되는 경우 : 부모 컴포넌트가 리렌더링 된다면 자식 컴포넌트도 무조건 리렌더링이 발생한다
function TodoList({ todos }) { return ( <ul> {todos.map((todo) => ( <li>{todo.text}</li> ))} </ul> ); }- 위 코드에서 key를 생략하면 콘솔에 경고가 출력된다. 🤔 key는 왜 필요할까
- 리액트에서 key는 리렌더링이 발생하는 동안 형제 요소 사이들 사이에서 동일한 요소를 식별하는 값이다.
- 동일한 자식 컴포넌트가 있을 때 리렌더링이 발생하면 current 트리와 workInProgress 트리 사이에 어떠한 컴포넌트가 변경이 있엇는지 구별해야 하는데 이 때 사용하는 것이 key다.
- 이 key로 어느 요소와 같은지 판단하기 때문에 key 값이 변경되면 새로운 값으로 인식해 리렌더링이 발생한다.
리액트 렌더링 프로세스
- 리액트에서 렌더링 프로세스가 시작되면 리액트는 컴포넌트의 루트에서부터 차근차근 아래쪽으로 내려가면서 업데이트가 필요하다고 지정돼있는 모든 컴포넌트를 찾는다.
- 만약 업데이트가 필요한 컴포넌트를 발견하면 클래스 컴포넌트는 클래스 내부의 render() 함수를 실행하고 함수형 컴포넌트의 경우 FunctionComponent() 그 자체를 호출한 뒤에 결과물을 저장한다.
- 렌더링 결과물은 JSX 문법으로 구성되어 있고, 자바스크립트로 컴파일 되면서 React.createElement()를 호출하는 구문으로 변환된다.
function Hello() { return ( <TestComponent a={35} b='yceffort'> 안녕하세요 </TestComponent> ) } function Hello() { return React.createElement( TestComponent, {a: 35, b:'yceffort'} '안녕하세요' ) }- 결과물은 아래와 같이 나온다. 렌더링 프로세스는 위와 같은 과정을 거쳐 컴포넌트의 렌더링 결과물을 수집한 다음, 리액트의 새로운 트리인 가상 DOM과 비교해 실제 DOM에 반영하기 위한 변경 사항을 수집한다.
{type: TestComponent, props: {a: '35', b: 'yceffort', children: '안녕하세요'}}렌더단계, 커밋단계
- 리액트 렌더링은 렌더단계와 커밋단계로 나눠서 실행한다.
- 렌더단계는 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업을 말한다. 즉 렌더링 프로세스에서 컴포넌트를 실행해 이 결과와 이전 가상 DOM을 비교하는과정을 거쳐 변경이 필요한 컴포넌트를 체크하는 단계이다. 여기서 비교하는 것은 type, props, key다.
- 커밋단계는 렌더 단계의 변경사항을 실제 DOM에 적용해 사용자에게 보여주는 과정이다. 이 단계가 끝나야 비로소 브라우저의 렌더링이 발생한다.
- 리액트가 DOM을 커밋단계에서 업데이트 하면 모든 DOM 노드 및 인스턴스를 가르키드록 리액트 내부의 참조를 업데이트 한다.
- 그 다음 클래스 컴포넌트의 경우는 componentDidMount, componentDidUpdate 메서드를 호출하고, 함수 컴포넌트에서는 useLayoutEffect 훅을 호출한다.
- 여기서 알 수 있는 점은 리액트의 렌더링이 일어난다고 해서 DOM 업데이트가 일어나는 것은 아니다. 변경사항이 없다면 커밋단계는 실행이 되지 않는다.
function A() { return ( <div className="App"> <h1>Hello React!</h1> <B /> </div> ); } function B() { const [counter, setCounter] = useState(0); function handleButtonClick() { setCounter((previous) => previous + 1); } return ( <> <label> <C number={counter} /> </label> <button onClick={handleButtonClick}>+</button> </> ); } function C({ number }) { return ( <div> {number} <D /> </div> ); } function D() { return <>리액트 재밌다!</>; }- A,B,C,D라는 컴포넌트가 있다. 리액트가 어떤 순서로 렌더링 되는지 확인해보자
- 1. B 컴포넌트의 setState가 호출된다.
- 2. B 컴포넌트의 리렌더링 작업이 렌더링 큐에 들어간다.
- 3. 리액트 트리는 최상단 부터 렌더링 경로를 검사한다.
- 4. A컴포넌트는 리렌더링이 필요한 컴포넌트로 표시돼 있지 않으므로 별다른 작업을 하지 않는다.
- 5. 그다음 하위 컴포넌트인 B 컴포넌트는 업데이트가 필요하다고 체크되어 있으므로 B를 리렌더링 한다.
- 6. 5번과정에서 B는 C를 반환한다
- 7. C는 props인 number가 업데이트 되었으므로 업데이트가 필요한 컴포넌트라고 체크한다
- 8. 7번 과정에서는 C는 D를 반환했다
- 9. D도 마찬가지로 업데이트가 필요하지 않다. 하지만 C가 렌더링이 되어 자식 컴포넌트 D도 렌더링이 되었다.
📘 정리
React 렌더링은 “함수 실행과 가상 DOM 계산”을 의미하며, DOM 조작은 diff 결과가 있는 경우에만 commit 단계에서 수행된다. 즉, 렌더링이 곧 “화면이 다시 그려진다”는 뜻은 아니다.
'프론트엔드' 카테고리의 다른 글
프로젝트를 하면서 고민점들 (라우팅 렌더링) (0) 2025.10.18 React 딥다이브 (1) 2025.10.17 React 딥다이브 (0) 2025.10.15 React 딥다이브 (0) 2025.10.15 Next Image 최적화와 SVG (0) 2025.10.12