리액트 렌더링
렌더링이란 단어는 많이 듣고 사용하지만, 명확히 개념을 설명하기는 어려워 정리를 해보려고합니다.
브라우저에서의 렌더링은 HTML과 CSS리소스를 기반으로 웹페이지에 필요한 UI를 그리는 과정을 말하고,
리엑트에서의 렌더링은 브라우저의 렌더링에 필요한 DOM tree를 만드는 과정을 말합니다.
좀 더 상세하게 말하자면 React application tree안에있는 모든 컴포넌트들이 자신의 props와 state를 가지고
어떻게 UI를 구성하고 이를 바탕으로 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 과정을 말합니다.
re-rendering
리엑트에서 렌더링은 유저가 처음 application에 진입하면 최초 렌더링이 발생합니다. (root component 호출)
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Image />);
그 이후에는 re-rendering이 발생하게 되는데, 이는 아래의 경우에 발생합니다.
- useState()의 두 번째 배열요소인 setter 실행
class component의 setState 실행 - useReducer()의 두 번째 배열요소인 dispatch가 실행
- props가 변경되는 경우
- 부모 컴포넌트가 렌더링 될 경우
- component의 key props가 변경되는 경우
- forceUpdate
key값이 필요한 이유?
key는 re-rendering이 발생하는 동안 형제 요소들 사이에서 동일한 요소를 식별하는 값이다.
re-rendering이 발생하면 변경 컴포넌트를 최소화하기위해 current tree와 workInProgress tree 사이에서
어떠한 컴포넌트가 변경이 있었는지 구별해야 하는데 이를 구별하는 값이 key값이다.
Key가 없다면 파이버 내부의 Sibling index만을 기준으로 판단하게 된다.
배열에서 index값은 말 그대로 각 요소의 순서번호인데, key에 리스트의 index값을 넣을 경우
배열 순서가 바뀔 때(추가, 삭제)마다 다시 연산하며 불필요한 비용이 발생한다.
따라서 key prop에는 요소의 고유값(uuid 등)을 전달해 불필요한 연산을 방지해야 한다.
리액트의 함수 컴포넌트에서 rendering이 일어나는 경우는 위의 6가지 상황밖에 없습니다.
예를들어 useState로 관리되지않는 변수의 값이 아무리 변경되어도 re-rendering은 일어나지 않습니다.
mobX나 redux같은 라이브러리가 상태를 관리하지만, mobx-react, react-redux로 위의 방법중 하나로 리렌더링을 발생시켜
관리된 상태를 react에 반영합니다.
Rendering process
위와같은 렌더링이 일어나야하는 상황이 생긴다면, react는 아래와 같은 순서로 작동하여 rendering을 하게됩니다.
- react는 component의 root에서부터 아래쪽으로 내려가면서 업데이트가 필요하다고 지정되어 있는 모든 컴포넌트를 찾는다.
- 찾은 컴포넌트는 Functioncomponent() 그 자체를 호출한 뒤에 그 결과물(JSX)을 저장한다.
- 결과물은 JavaScript로 compile되면서 React.createElement()를 호출하는 구문으로 변환된다.
(브라우저의 UI구조를 설명할 수 있는 일반 자바스크립트 객체)
// JSX
function Hello () {
return (
<TestComponent a={30} b='hi'>
안녕하시렵니까.
</TestComponent>
)
}
위의 JSX 구문은 아래와같은 React.createElement를 호출해서 변환됩니다.
funciton Hello () {
return React.createElement(
TestComponent,
{a:30, b:'hi'},
'안녕하시렵니까.'
)
}
결과물은 아래와 같습니다.
{type: TestComponent, props: {a:30, b:'hi', children : '안녕하시렵니까.'}
이러한 과정을 거쳐서 각 컴포넌트의 rendering 결과물을 수집한다음,
가상 DOM과 비교해 실제 DOM에 반영하기위해 모든 변경사항을 차례로 수집합니다.
이렇게 계산하는 과정을 파이버 재조정자라고 합니다.
재조정 과정이 모두 끝나면 모든 변경사항을 하나의 동기 시퀀스로 DOM에 적용하여 변경된 결과물이 보이게 됩니다.
[파이버 재조정자 내용 참고]
Render / Commit
위와 같은 렌더링 과정은 commit과 render 두가지 단계로 나뉩니다.
Render Phase 렌더 단계
컴포넌트를 렌더링하고 변경 사항을 계산하는 모든작업을 말합니다.
즉, 렌더링 프로세스에서 컴포넌트를 실행해 (render() 또는 return)이 결과와 이전 가상 DOM을 비교하는 과정을 거쳐서
변경이 필요한 컴포넌트를 체크하는 단계입니다.
여기서 비교하는 것은 크게 3가지로 type, props, key 입니다.
Commit Phase 커밋 단계
렌더 단계의 변경사항을 실제 DOM에 적용해 유저에게 보여주는 과정을 말합니다. 이 단계가 끝나야 브라우저의 렌더링이 발생합니다.
React가 먼저 DOM을 commit단계에서 업데이트하면, 만들어진 모든 DOM 노드 및 인스턴스를 가리키도록 리엑트 내부의 참조를 업데이트 합니다. (class-componentDidMount, componentDidUpdate / function - useLayoutEffect hook)
react의 rendering이 일어난다고 해서 무조건 DOM 업데이트가 일어나지는 않습니다.
변경사항을 계산하는 rendering이 일어났지만 아무런 변경사항이 감지되지 않는다면, commit단계는 생략될 수 있다는 것입니다.
즉, react의 렌더링은 꼭 가시적인 변경이 일어나지 않아도 발생할 수 있습니다.
'Study > React' 카테고리의 다른 글
React 깊이 이해하기(4) - useState (1) | 2024.11.09 |
---|---|
React 깊이 이해하기(2) - Life cycle (0) | 2024.11.07 |
React 깊이 이해하기(1) - 가상 DOM (Virtual DOM), Fiber tree (0) | 2024.10.25 |