민프
[React] React란? 동작원리는 어떻게 될까? 본문
리엑트란?
https://ko.legacy.reactjs.org/
공식 홈페이지를 보면 아래와 같이 나와있다.
React는 사용자 인터페이스를 구축하기 위한 선언적이고 효율적이며 유연한 JavaScript 라이브러리입니다. “컴포넌트”라고 불리는 작고 고립된 코드의 파편을 이용하여 복잡한 UI를 구성하도록 돕습니다.
- React는 사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리이다.
- UI를 구성하는 컴포넌트 기반의 개발을 지원한다.
- React에서의 "가상 DOM"은 React가 UI 업데이트를 효율적으로 처리하기 위해 사용하는 개념이다.
공홈 소개글에서 나와있는대로 정리해보면 위 와 같은데
위에서 언급한 복잡한 인터페이스 UI를 구성하도록 돕는 부분이 사실 상 React의 장점인데
이것을 구현할 수 있도록 해준게 바로
Virtual DOM이다.
그럼 Virtual DOM이 뭔지 한번 알아보기전에 DOM이란 무엇인지부터 알아보자
돔 (DOM) 이란?
네이버 사전에 의하면
DOM(Document Object Model)은 문서 객체 모델을 의미한다.
여기에서 문서 객체 모델(DOM)이란 무엇일까? 간략하게 정리해보면 아래와 같다.
- 쉽게 말해서 웹 페이지를 이루는 html, head, body와 같은 태그들을 javascript가 이용할 수 있는 (메모리에 보관할 수 있는) 객체 모델을 의미한다.
- DOM은 "Document Object Model"의 약어로, 웹 페이지의 구조화된 내용을 표현하고 상호작용할 수 있는 방법을 제공하는 프로그래밍 인터페이스이다.
DOM은 웹 페이지의 요소들을 계층 구조로 나타내며, 각 요소는 객체로 표현됩니다.
이 객체들은 JavaScript와 같은 스크립팅 언어를 사용하여 동적으로 조작하거나 상호작용할 수 있는 인터페이스를 제공합니다.
예를 들어, DOM을 사용하면 페이지의 특정 요소의 내용을 변경하거나 스타일을 수정하거나 이벤트를 처리할 수 있습니다.
예시로 아래 코드를 봐보자면
<!DOCTYPE html>
<html>
<head>
<title>DOM Example</title>
</head>
<body>
<h1>Hello, DOM!</h1>
<p>DOM 예시 코드 입니다.</p>
</body>
</html>
이 HTML 코드를 DOM으로 표현하면 아래와 같다
Document
├── html
│ ├── head
│ │ └── title
│ │ └── Text: "DOM Example"
│ └── body
│ ├── h1
│ │ └── Text: "Hello, DOM!"
│ └── p
│ └── Text: "DOM 예시 코드 입니다."
JavaScript를 사용하여 이 DOM에 접근하고 조작할 수 있습니다.
예를 들어, 아래와 같이 DOM을 사용하여 제목과 단락의 내용을 변경할 수 있습니다
const titleElement = document.querySelector('h1');
titleElement.textContent = 'DOM을 변경합니다';
const paragraphElement = document.querySelector('p');
paragraphElement.textContent = '여기도 DOM을 변경합니다';
여기까지가 간단하게 DOM을 정리해보았는데, 이제 가상돔을 이어서 알아보자
가상돔 (Virtual DOM) 이란?
https://ko.legacy.reactjs.org/docs/faq-internals.html
Virtual DOM (VDOM)은 UI의 이상적인 또는 “가상”적인 표현을 메모리에 저장하고
ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화하는 프로그래밍 개념입니다. 이 과정을 재조정이라고 합니다.
공식홈페이지에서 나와있는 설명은 위 와 같은데 정리를 해보자면 아래와 같습니다
- 가상DOM이란 실제DOM의 구조를 분석하여, 아래와 같은 형태로 메모리에 저장하고 관리하는 객체라고 볼 수 있다.
- 렌더링시마다 새로운 가상DOM을 생성하여, 상태값 변경 이전/이후 달라진 부분을 비교하는 매커니즘을 사용한다.
가상 DOM은 실제 브라우저 DOM과 별개로 React가 메모리 상에 유지하는 가상적인 DOM 트리인데요.
React의 가상 DOM은 실제 DOM과 동기화되어 있고,
React 컴포넌트의 상태나 프로퍼티가 변경될 때마다 가상 DOM을 업데이트합니다.
그런 다음 React는 가상 DOM의 변화를 분석하여 실제 DOM에 필요한 최소한의 변경만을 적용합니다.
이러한 접근 방식은 브라우저의 성능을 향상시키고 업데이트 과정을 최적화하는 데 도움이 됍니다.
그럼 실제 DOM의 어떤 문제를 해결하기 위해 가상돔이 나오게 되었는지 알아보자
가상돔은 무엇을 해결하기 위하여 나오게 되었을까?
- DOM 렌더링에 대한 비효율적인 문제
결론부터 말하자면 DOM 렌더링에 대한 비효율적인 문제가 있습니다.
React는 SPA(Single Page Application) 인데요
SPA이기 때문에 DOM의 복잡도가 증가하게 되고, 렌더링을 더 자주하게 된다.
또한 그 만큼 PC의 자원을 더 소모하게 되는 문제가 반복되게 됩니다.
Document
├── html
│ ├── head
│ │ └── title
│ │ └── Text: "DOM Example"
│ └── body
│ ├── h1
│ │ └── Text: "Hello, DOM!"
│ └── p
│ └── Text: "DOM 예시 코드 입니다."
위에서 언급 한 DOM을 가져와봤는데 위 DOM구조에 접근해서 아래 코드를 적용하면 내용을 바꾸거나 각 속성을 바꿀 수 있는데요
const titleElement = document.querySelector('h1');
titleElement.textContent = 'DOM을 변경합니다';
const paragraphElement = document.querySelector('p');
paragraphElement.textContent = '여기도 DOM을 변경합니다';
이렇게 querySelector를 사용하거나 jQuery와 같은 라이브러리를 사용하여 속성을 바꾸면 DOM이 변경 된 것을 인지하고 브라우저 엔진은 렌더링을 다시하였습니다.
위 사진은 브라우저의 기본 구조를 보여주고 있는데 우리가 봐야할 부분은 아래 3곳 입니다.
- 사용자 인터페이스 - 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.
- 브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
(ex. 크롬 - 블링크, 웹키트...)
- 렌더링 엔진 - 브라우저 엔진으로부터 전달받은 HTML, CSS를 파싱하여 요청한 콘텐츠를 표시.
(ex. HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함 )
간단하게 말해보자면
브라우저 엔진은 서버에서 받은 자료를 기반으로 렌더링 엔진을 통해 요청받은 콘텐츠를 표시하게 되는데요.
아래 사진은 렌더링 엔진의 기본적인 동작 과정인데요
그림 1은 렌더링 엔진이 어떻게 동작하는지 큰 틀에서 보여주고 있고,
실제 크롬의 렌더링 엔진의 모체가 되는 웹킷 의 동작 과정을 보여주고 있는 사진입니다.
- HTML을 파싱해서 DOM트리를 구축하고, CSS를 파싱해서 스타일 규칙을 만든다.
- 위 두개를 합쳐서 렌더트리를 만들고
- 렌더 트리를 배치 합니다.
- 이 렌더 트리를 기준으로 레이아웃을 배치하고 스타일 작업 등을 하게 됩니다.
즉, querySelector를 사용하거나 jQuery와 같은 라이브러리를 사용하여 속성을 바꾸면 DOM이 변경 된 것을 인지하고 브라우저 엔진은 위 와 같은 렌더링을 다시하였습니다.
그럼 이러한 불필요한 렌더링에 대한 부분을 가상돔에서는 어떻게 해결하였을까?
위에서 언급했던 가상돔이라는 DOM을 가상의 객체 메모리에 만들어놓는 것입니다.
가상돔의 실행순서를 보면 아래와 같습니다.
1. 상태 및 프로퍼티 변경 감지: React 컴포넌트의 상태나 프로퍼티가 변경되면, React는 가상 DOM에 변경 사항을 기록합니다.
2. 가상 DOM 업데이트: 변경된 부분만을 포함하는 가상 DOM 트리가 생성됩니다. 이 트리는 메모리 내에서만 존재하며, 실제 DOM과는 별개입니다.
3. 가상 DOM 비교: 이전 가상 DOM과 새로운 가상 DOM을 비교하여 변경된 부분을 식별합니다. 이 과정을 통해 어떤 요소가 추가되었는지, 변경되었는지, 제거되었는지 등을 판단합니다.
4. 실제 DOM 업데이트: 변경된 부분만을 실제 DOM에 적용합니다. 이 때 최소한의 DOM 조작만을 수행하여 성능을 최적화합니다.
쉽게 말하면 마지막 부분처럼 가상돔과 실제돔을 비교해서 변경된 부부만을 실제 DOM에 적용함으로써
불필요한 렌더링을 해결하였습니다.
여기서 중요한 점은 그럼 React는 최고의 방법일까?
제가 생각했을때에는 최고의 방법은 아닙니다.
왜냐하면 어쨋뜬 가상DOM을 만들어내야하고, 반영하기 위해 무언가를 계속 만들어야하기 때문에 실제 DOM에는 적용되지 않는 연산이 분명히 들어갈 것이다.
따라서 현재 내가 어떤 서비스를 개발하느냐에 따라서 기술 선택을 해야하는게 중요한 것 같습니다.
다음으로 React의 생명주기에 대해서 알아보겠습니다.
React의 생명주기
위 사진은 클래스형 컴포넌트의 생명주기 기능을 나타내고 있는데
React의 16.8 업데이트 이후로 함수형 컴포넌트가 나오게 되면서 클래스 컴포넌트의 기능을 Hook을 통해 함수형 컴포넌트도 사용할 수 있게 되었습니다.
그럼 클래스 형 컴포넌트와 함수형 컴포넌트를 비교해가면서 어떻게 생명주기를 다루는지 알아보겠습니다.
Mounting
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
순으로 진행되는데 하나하나 살펴보자
1. Constructor
- 역할: 컴포넌트가 생성될 때 초기 설정을 담당
- 사용 시기: 컴포넌트 인스턴스가 생성될 때 호출
클래스형 컴포넌트 - constructor
class ClassComponent extends React.Component {
constructor(props) {
super(props);
// 초기화 및 상태 설정
}
}
함수형 컴포넌트 - useState
function FunctionalComponent(props) {
// 초기화 및 상태 설정
}
2. static getDerivedStateFromProps
- 역할: 이 메서드는 새로운 프로퍼티(props)를 받았을 때, 해당 프로퍼티를 기반으로 컴포넌트의 상태(state)를 업데이트합니다. 이전의 componentWillReceiveProps 메서드보다 더 예측 가능한 방식으로 동작합니다.
- 사용 시기: 새로운 프로퍼티가 받아질 때마다 호출됩니다.
클래스형 컴포넌트
class ClassComponent extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
// nextProps와 prevState를 사용하여 상태 업데이트
if (nextProps.value !== prevState.value) {
return { value: nextProps.value };
}
return null; // 상태 업데이트 없음
}
}
함수형 컴포넌트
import React, { useState, useEffect } from 'react';
function FunctionalComponent(props) {
const [value, setValue] = useState(props.value);
useEffect(() => {
if (props.value !== value) {
setValue(props.value);
}
}, [props.value]);
// ...
}
3. render
- 역할: 메서드는 React 컴포넌트의 UI를 정의하고,
JSX나 React 엘리먼트를 반환하며, 이를 기반으로 실제 화면에 렌더링되는 컴포넌트의 구조와 내용을 생성합니다. - 사용시기: 클래스형 컴포넌트에서 render() 메서드는 컴포넌트가 렌더링될 때마다 호출됩니다.
클래스형 컴포넌트
class ClassComponent extends React.Component {
render() {
return (
<div>
{/* JSX 및 React 엘리먼트를 반환 */}
</div>
);
}
}
함수형 컴포넌트
function FunctionalComponent() {
return (
<div>
{/* JSX 및 React 엘리먼트를 반환 */}
</div>
);
}
4. ComponentDidMount()
- 역할: 컴포넌트가 마운트된 후 작업을 수행합니다.
- 사용 시기: 컴포넌트가 마운트된 직후에 호출됩니다.
클래스형 컴포넌트
class ClassComponent extends React.Component {
componentDidMount() {
// 컴포넌트 마운트 후 작업
}
}
함수형 컴포넌트
import React, { useEffect } from 'react';
function FunctionalComponent() {
useEffect(() => {
// 컴포넌트 마운트 후 작업
}, []);
}
Update
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
1. static getDerivedStateFromProps
위에서 설명
2. shouldComponentUpdate()
- 역할
- shouldComponentUpdate(nextProps, nextState) 메서드는 컴포넌트가 업데이트되기 전에 호출되며, 현재 프로퍼티와 상태, 그리고 다음 프로퍼티와 상태를 인자로 받습니다.
- 리렌더링 여부를 결정하는 로직을 작성하여 true 또는 false를 반환합니다.
- true를 반환하면 컴포넌트는 리렌더링되며, false를 반환하면 리렌더링을 건너뛰게 됩니다.
- 사용시기
- shouldComponentUpdate()는 업데이트가 발생할 때마다 호출됩니다.
- 리렌더링 조건을 기반으로 컴포넌트가 리렌더링되어야 할지 여부를 결정하는 데 사용됩니다.
클래스형 컴포넌트
class ClassComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 변경 여부를 판단하는 로직
return nextProps.value !== this.props.value;
}
render() {
// ...
}
}
함수형 컴포넌트
const FunctionalComponent = React.memo((props) => {
// ...
});
3. render
위에서 설명
4. getSnapshotBeforeUpdate()
- 역할
- getSnapshotBeforeUpdate(prevProps, prevState) 메서드는 컴포넌트 업데이트가 발생하기 직전에 호출됩니다.
- 업데이트 전의 DOM 상태를 캡처하거나 다른 정보를 저장하여, 업데이트 이후에 사용할 수 있는 스냅샷(snapshot)을 반환합니다.
- 사용 시기
- 컴포넌트가 업데이트되기 직전에 호출됩니다.
- 주로 DOM 상태나 위치 정보를 저장하거나, 특정 상태 값을 기반으로 계산한 데이터를 저장하는 데 활용됩니다.
클래스형 컴포넌트
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.scrollRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 업데이트 전 DOM 상태를 캡처하여 반환
return this.scrollRef.current.scrollTop;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 업데이트 후 작업 및 이전 DOM 상태 복원
if (snapshot !== null) {
this.scrollRef.current.scrollTop = snapshot;
}
}
render() {
return (
<div ref={this.scrollRef} style={{ overflow: 'auto', maxHeight: '300px' }}>
{/* ... */}
</div>
);
}
}
함수형 컴포넌트
import React, { useState, useEffect, useRef } from 'react';
function FunctionalComponent() {
const scrollRef = useRef(null);
const [prevScrollTop, setPrevScrollTop] = useState(null);
useEffect(() => {
if (prevScrollTop !== null) {
scrollRef.current.scrollTop = prevScrollTop;
}
}, [prevScrollTop]);
const handleScroll = () => {
// 스크롤 이벤트 처리
};
return (
<div ref={scrollRef} style={{ overflow: 'auto', maxHeight: '300px' }} onScroll={handleScroll}>
{/* ... */}
</div>
);
}
5. componentDidUpdate()
- 역할
- componentDidUpdate(prevProps, prevState, snapshot) 메서드는 컴포넌트 업데이트가 발생한 후 호출됩니다.
- 업데이트 이후에 수행할 작업을 정의하거나, 이전 상태와 프로퍼티를 기반으로 추가적인 작업을 수행합니다.
- 사용 시기
- 컴포넌트 업데이트가 발생한 후에 호출되며, 렌더링이 완료된 이후에 실행됩니다.
- 주로 업데이트 이후에 필요한 추가 작업이나 DOM 조작을 수행하는 데 사용됩니다.
클래스형 컴포넌트
class ClassComponent extends React.Component {
componentDidUpdate(prevProps, prevState) {
// 업데이트 후 작업 수행
if (this.props.value !== prevProps.value) {
console.log('Value changed:', this.props.value);
}
}
render() {
// ...
}
}
함수형 컴포넌트
import React, { useEffect, useRef } from 'react';
function FunctionalComponent(props) {
const prevPropsRef = useRef(props.value);
useEffect(() => {
if (prevPropsRef.current !== props.value) {
console.log('Value changed:', props.value);
}
prevPropsRef.current = props.value;
}, [props.value]);
return (
// ...
);
}
Unmount
- componentWillUnmount
1. componentWillUnmount
- 역할
- componentWillUnmount() 메서드는 컴포넌트가 언마운트되기 직전에 호출됩니다.
- 컴포넌트가 제거되기 전에 필요한 정리 작업을 수행합니다. 이는 주로 리소스 해제, 타이머 해제, 이벤트 리스너 등을 포함합니다.
- 컴포넌트가 다른 화면으로 이동하거나 더 이상 필요하지 않을 때 수행해야 하는 작업을 여기에서 처리할 수 있습니다.
- 사용시기
- componentWillUnmount()는 컴포넌트가 언마운트되기 전에 호출되므로, 컴포넌트가 화면에서 제거되기 직전에 실행되는 로직을 작성할 때 사용됩니다.
클래스형 컴포넌트
import React from 'react';
class ClassComponent extends React.Component {
componentWillUnmount() {
/* 언마운트 시 작성 */
}
render() {
return (
<div>
Value: {this.state.value}
</div>
);
}
}
export default ClassComponent;
함수형 컴포넌트
function FunctionalComponent(props) {
const scrollRef = useRef(null);
useEffect(() => {
// 업데이트 후 작업
return () => {
// 컴포넌트 언마운트 시 작업 (클린업)
};
}, [/* 의존성 배열 */]);
// ...
}
위 와 같이 React는 무엇인지, 동작 과정은 어떻게 되는지, 가상돔은 무엇인지 마지막으로 생명주기에 대해서 알아보았습니다.
참고링크
https://ko.legacy.reactjs.org/
https://ko.legacy.reactjs.org/docs/faq-internals.html
https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model/Introduction
https://terms.naver.com/entry.naver?docId=866580&cid=50376&categoryId=50376
https://d2.naver.com/helloworld/59361
'[React]' 카테고리의 다른 글
[React] S3 + CloudFront를 이용해서 배포해보자 (0) | 2023.09.18 |
---|---|
[React][TypeScript] Build 최적화하기 (Terser, react-app-rewired) (0) | 2023.07.05 |
[React] 가로 스크롤 바 안보이게 하기 [기록] (0) | 2023.06.27 |
[React][TypeScript][Library] 날짜, 시간 카운트다운 라이브러리 react-countdown를 사용해보자 (0) | 2023.06.20 |
[React][TypeScript] SockJS, STOMP 연결 (기록) (0) | 2023.06.20 |