1. 개요
React를 공부하면서 어떻게 보면 가장 기초이고, 가장 중요한 부분인데 사실 이제야 포스팅한다는 것은 이제까지 리액트를 잘못 사용해왔다는 반증이기도 한데... 조금 더 심도있게 리액트를 알아보기 위해서 렌더링이란 무엇이고, 렌더링의 조건이 어떻게 되는 건지 확인차 적어보려고 합니다.
2. 렌더링이란?
아래의 Welcome을 컴포넌트라고 부릅니다. (정확히는 함수 컴포넌트)
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
리액트에서 렌더링이란 컴포넌트가 현재 props와 state의 상태에 기초해 UI를 어떻게 구성할지, 컴포넌트에게 작업을 요청하는 것을 의미합니다.
3. 렌더링 과정
3-1. React의 렌더링
기본적인 리액트의 렌더링 개념은 루트(<div id="root"></div>) DOM부터 시작해 플래그가 지정되어 있는 모든 컴포넌트를 찾아서 렌더링을 진행하는 것을 말합니다.
* root id는 public/index.html에 기본적으로 설정이 되어있습니다.
* DOM은 HTML parser에 의해 생성된 트리 구조의 Node 객체 모델입니다.
const root = ReactDOM.createRoot(
document.getElementById('root')
);
const element = <h1>Hello, world</h1>;
root.render(element);
기본적으로 리액트의 렌더링은 이러한 DOM 엘리먼트를 ReactDOM.createRoot()에 전달한 다음, 그 React Element를 root.render()에 전달하는 과정을 거칩니다.
3-2. React Element
자, 그럼 React Element는 무엇일까요? 리액트 엘리먼트는 type과 props를 가지는 React만의 객체입니다. React.creatElement()를 이용해 만들 수 있으며, type으로 HTML 태그 이름을 가지고, 그 이외의 특징을 props로 관리하는 객체 형태로 정의됩니다.
// createElement를 이용해서 React Element 만들기
React.createElement(
'div',
{ className: 'name' },
'React'
)
// 위와 같은 의미 (JSX 문법)
<div className='name'>React</div>
// createElement를 이용해서 만들어진 React Element 객체
{
type: 'div',
props: {
className: 'name',
children: 'React'
}
}
사실 위와 같은 createElement()라는 함수를 사용하기가 어렵기도 하고, 직관적이지도 않습니다.
따라서 JSX 문법을 사용해 보통 위의 객체를 DOM 형태(<div className='name'>React</div>)로 정의하여 사용합니다.
이러한 리액트 엘리먼트는 불변객체로 작용해서, 엘리먼트가 생성된 이후에는 해당 엘리먼트의 속성과 자식을 변경할 수 없다는 특징을 가지게 됩니다.
3-2. Virtual DOM
리액트의 특징 중 하나의 가상 돔(Virtual DOM)은 실제 DOM의 구조와 비슷한 React 객체의 트리를 말합니다.
웹 브라우저에서 사용자는 여러 가지 액션을 통해 웹 서버에 요청하고, 그에 따른 응답으로 DOM 구조가 빈번하게 바뀌는데, 이때마다 DOM이 수정되어 Render Tree가 생성되고, Reflow, Repaint의 과정을 계속 수행하게 된다면 굉장히 답답한 상황이 발생하게 될 것입니다.
이때 React에서는 Virtual DOM을 실제 DOM에 필요한 부분만 적절하게 반영해서 불필요한 수정이 일어나지 않도록 해주는데, 이 Virtual DOM의 가장 큰 장점은 개발자가 직접 DOM을 조작하지 않아도 된다는 점이고, 이러한 과정을 모두 자동화해준다는 점입니다.
또한 DOM의 수정을 batch로 한 번에 처리하기 때문에 리렌더링 연산을 최소화 할 수 있습니다.
3-3. 재조정(Reconciliation)
앞서 설명한 Virtual DOM에서 어떻게 리액트는 실제 DOM과 가상 DOM을 구별하는지 궁금할 텐데, 결과적으로 브라우저에 렌더링을 할 때 어떻게 기존의 컴포넌트와 변경이 되었는지 확인하기 위해 리액트에서 diffing 알고리즘을 사용하여 이 알고리즘을 통해 컴포넌트의 갱신합니다.
리액트는 아래의 2가지 가정을 기반으로 O(n)의 시간복잡도를 가지는 휴리스틱 알고리즘을 구현했습니다.
- 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어 낸다.
- 개발자가 key prop을 통해 컴포넌트 인스턴스를 식별하여, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야할지 표시해 줄 수 있다.
위의 재조정 단계를 거쳐 이전 elements와 새로 생성된 elements를 비교해 엘리먼트가 변경되었다면 렌더링을 수행합니다.
4. 리렌더링
앞에서 렌더링을 설명했는데, 리액트에선 초기에 한번 렌더링을 진행하고, 그 이후에 특정 조건이 발생하면 다시 렌더링을 진행하는 리렌더링이라고 하는 것이 있습니다.
- 내부 상태(state) 변경시
- 부모에게 전달받은 값(props) 변경시
- 중앙 상태값(Context value 혹은 redux store) 변경시
- 부모 컴포넌트가 리렌더링 되는 경우
위의 경우가 컴포넌트가 리렌더링 되는 조건입니다.
리액트가 아무리 최적화가 잘 되어있다고해도, 무분별하게 렌더링이 일어날 경우 성능 저하가 일어나게 되기 때문에, 이러한 조건들을 기준을 두고 코드를 작성하여 무분별하게 렌더링이 일어나지 않도록 주의하여야 합니다.
* redux store 변경시 자동으로 리렌더링이 되는 이유는, 리덕스 스토어가 <Provider store={store}>로 컴포넌트를 감싸주었을 때, 스토어 상태가 변경될 때마다 이를 참조하는 컴포넌트들이 리렌더링이 될 수 있도록 react-redux 라이브러리가 자동적으로 컴포넌트 들의 렌더 함수들을 subscribe 해주기 때문입니다.
4-1. 리렌더링 과정
- 위의 조건을 통해 컴포넌트 리렌더링
- 구현부 실행 = props 취득, hook 실행, 내부 변수 및 함수 재 생성
- return 실행, 렌더링 시작
- 렌더 단계(Render Phase): 새로운 가상 DOM 생성 후 이전 가상 DOM과 비교해 달라진 부분을 탐색하고 실제 DOM에 반영할 부분을 결정
- 커밋 단계(Commit Phase): 달라진 부분만 실제 DOM에 반영
- useLayoutEffect: 브라우저가 화면에 Paint하기 전에 useLayoutEffect에 등록해둔 effect(부수 효과 함수)가 동기적으로 실행되며, 이때 state, redux store 등의 변경이 있다면 한번 더 리렌더링
- Paint: 브라우저가 실제 DOM을 화면에 그림. didUpdate 완료.
- useEffect: update되어 화면에 그려진 직후, useEffect에 등록해둔 effect(부수 효과 함수)가 비동기로 실행
'FrontEnd > React 심화' 카테고리의 다른 글
[React] HOC(고차 컴포넌트) 함수형 예제 (0) | 2022.11.15 |
---|---|
[React] useMemo와 useCallback의 차이 (5) | 2022.11.03 |
[React] SEO 처리 하기 (0) | 2022.10.19 |
[React] SSR과 CSR(SPA) 차이 (0) | 2022.10.19 |
[React] Redux Toolkit 에서의 비동기 처리(createAsyncThunk, extraReducers) (0) | 2022.09.19 |