React의 VirtualDOM
9/6/2022
작성자 : 홍원배
DOM
바닐라 JS의 DOM 조작은 아래와 같은 이유로 성능에 문제를 일으킨다
- DOM을 Repaint 하는 작업
- DOM의 Layout을 계산해서 Refloat 하는 작업
반면, 리액트는 Declarative하기에 DOM조작은 ReactDOM 라이브러리에 맡기고, 화면이 어떻게 보여지는가에만 집중할 수 있게 한다. (그렇기에 DOM을 직접적으로 조작하는 useRef는 지양해야 한다.)
How가 아니라 What을 그려내기 위해 리액트에 내장된 Component 라이브러리의 기능을 불러온 후 , 여기에 내장된 render() 메소드를 통해 가상 DOM객체를 만들고 모듈을 통해 ReactDOM 라이브러리에게 rendering될 컴포넌트가 전달되어 현재 DOM과 전달받은 컴포넌트를 비교하여 변경이 필요한 부분(컴포넌트 단위)만 변화를 주어 화면에 보여주게 된다.
export default Class App extends React.Component { render() { return ( <div>홍원배 블로그에 오신것을 환영합니다</div> ) }
[ state ⇒ component ⇒ 가상 DOM ⇒ 실제 DOM과 비교 ⇒ 화면에 그리기 ] 과정이 일방향적으로 이루어진다.
심화적으로는, 이전버전의 가상 DOM과 현재의 가상 DOM을 비교하여 재조정하는 작업을 render phase라고 하며, 이후 비교된 결과로 실제로 DOM을 변경하는 것을 commit phase라고 한다.
이러한 과정을 효율적으로 하기 위해 useCallback, useMemo, memo 등을 사용할 수 있는데, 예를 들어 useCallback 훅을 사용하면 ‘해당 함수를 기억’할 수 있게 되고, props로 전달되어지는 하위 컴포넌트로의 함수를 동일한 참조값으로 취급하여 commit phase를 방지 할 수 있다. Render phase 역시 방지 하기 위해서는 memo를 같이 사용하면 된다.
export default function Parents() { function onClick= useCallback(() => {}, []) // 함수는 매번 새로 만들어지는 참조값 // useCallback을 이용하여 참조값을 동일하게 기억한다 return ( <Childs onClick={onClick}/> ) export default function Childs({onClick}) { return ( <FirstChild /> <SecondChild /> )
React Element
const element = <h1>Hello, world</h1>; // babel은 JS에서 html를 사용하는 이 이상한 문법(JSX)을 // React.createElement(태그명, 속성객체, children)으로 바꿔주어 실행해서 // 자바스크립트 객체(가상DOM)을 생성한다 /* 최신버전의 리액트의 JSX는 변경되어 React.createElement()를 활용하지 않는다 그러므로 import React를 17버전부터는 안해도 된다 https://ko.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html */ //최종적으로 컴포넌트는 가상DOM 객체를 return한다
브라우저 DOM Element (객체로 이루어진 트리구조 모델)와 다른 React Element의 정체는 일반객체(Plain Object)였다. React Component는 그러한 React Element를 반환하는 함수일뿐이다.
- react extension에서 highlight되는 ReactComponent는 해당 컴포넌트가 실행(RenderPhase) 되었다는 것을 말한다
- React Hook은 state와 생명주기 기능을 연동(hook into)한다
- 리액트를 쓰는 이유는 관심사의 분리다 + 가상돔을 활용한 렌더링 효율화
⇒ UI만을 담당하는 컴포넌트와 BusinessLogic(과제에선 상태값 조정) 컴포넌트로 분리한다
이벤트
- 리액트의 이벤트는 합성이벤트(SyntheticEvent)이다
- 바닐라 자바스크립트에서는 DOM노드에 직접 이벤트리스너(이벤트가 발생했을 때 그 처리를 담당하는 함수)를 연결하지만,
- 리액트에서는 최상위 부모인 document요소에 연결된 이벤트리스너에서 일괄 처리한다.