Virtual DOM
DOM 리마인드
브라우저는 화면을 그리기 위해 DOM을 사용한다. 브라우저는 javascript와 같은 스크립트 언어만 읽을 수 있기 때문에 html에 접근하기 위해 html태그들을 트리구조로 객체화 한다. 이렇게 객체화된 html 문서를 DOM, Document Object Model 이라고 한다.
웹페이지의 규모가 커지다 보면 이러한 DOM의 크기도 커지게 된다.
DOM에 변경사항이 생기면 DOM을 재구축하고, 리플로우와 리페인트가 일어나게 되는데 DOM의 크기가 크면 이러한 과정에 오랜 시간이 걸린다.
또한 변경사항이 없는 컴포넌트나 요소들도 DOM 재구축 과정에서 삭제되고 재생성 되기도 하고, 다시 리플로우가 일어나기 때문에 비효율적이다.
이러한 비효율을 해결하기 위해 Virtual DOM 이라는 개념이 생겨났다.
Virtual DOM
Virtual DOM은 실제 화면에는 표현되지 않는 가상의 DOM이다. 가상 DOM은 DOM의 메타데이터로 일종의 DOM 요약본이라고 할 수 있다.
이렇게 가상의 DOM을 만드는 이유는 실제 DOM에 변경사항을 반영하기 전에 어떤 부분이 바뀌었는지 확인하고 변경이 필요한 부분만 다시 DOM을 그려주기 위해서이다.
Virtual DOM은 실제 DOM과 동기화되어 상태가 변경될 때 마다 가상 DOM을 새로 생성하여 이전 상태와 비교한다.
이렇게 하는 이유는 실제 DOM에는 브라우저가 화면을 그리는데 필요한 모든 정보가 들어있어 실제 DOM을 조작하는 작업은 무거운 작업이기 때문이다. Virtula DOM은 javascript 객체로 이루어져 있기 때문에 훨씬 간단하게 DOM을 조작할 수 있다.
React Diffing Algorithm
이렇게 실제 DOM과 Virtual DOM을 비교하여 변경하고 다시 렌더링해야 하는데, 일반적인 방법으로는 렌더 트리를 변경하는데 시간 복잡도 O(n^3)의 매우 비효율적으로 작동한다.
그렇기 때문에 React에서는 효율적으로 렌더트리를 변경하기 위해서 시간복잡도 O(n)의 휴리스틱 알고리즘(Heuristic Algorithm)을 사용하여 렌더트리를 변경한다.
* 휴리스틱(heuristic) : 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나, 체계적이면서 합리적인 판단이 굳이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있게 보다 용이하게 구성된 간편추론의 방법이다.
이러한 알고리즘에는 두 가지 가정이 필요하다.
⓵ 서로 다른 두 요소는 다른 트리를 구축한다.
⓶ key 프로퍼티를 가지고 렌더 트리가 변경될 때 변경되지 않아도 되는 자식 요소가 무엇인지 파악할 수 있다.
실제로 이러한 가정은 대부분의 상황에서 유효하다.
Diffing Algorithm
React에서 실제 DOM과 가상 DOM을 비교할 때, 같은 level에 있는 요소들을 비교한다.
⓵ 요소의 타입이 다른 경우
각각의 Node를 비교하여 서로 다른 요소이면(예를 들면 <div> vs <a> / <div> vs <span>) 해당 node 이하의 tree는 파괴해버리고(자식들까지 모두!) 다시 새롭게 해당 node부터 tree를 생성한다.
⓶ 요소의 타입이 같은 경우
만약 같은 요소이면 해당 Node의 속성(attribute)을 비교한다. 속성이 다르다면 속성만 새로운 값으로 할당한다.
⓷ 같은 타입의 Component의 경우
Component의 요소들은 전부 유지되고, props로 내려주는 값을 변경시킨다.
⓸ 자식요소의 재귀적 처리
<ul>
<li>사과<li>
<li>딸기<li>
<li>포도<li>
<ul>
위와 같은 요소들이 있을 때 <ul> 태그의 마지막 요소에 <li> 태그를 추가하는 것은 다른 요소들은 그대로 있고 <li> 태그 하나만 추가되는 것이기 때문에 문제가 되지 않는다.
그런데 가장 첫 요소에 <li>태그를 추가하는 경우 react는 첫 <li>요소를 검사하고 기존의 <li>요소와 다르기 때문에 뒤에있는 모든 <li>태그들을 파괴해버리고 새롭게 전부 DOM에 그리게 된다.
이러한 현상을 방지하기 위해 react에서 반복적인 요소를 그릴 때 key 속성을 부여하라고 권장한다. 실제로 <li> 태그에 key 속성이 있으면 처음에 요소를 추가해도 다시 전부 그리지 않고, 하나의 요소만 추가한다.
React Hooks
Hook은 함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드를 의미한다.
우리가 기존에 사용하던 useState 와 useEffect 같은 것도 React Hook이다.
Hook에는 두 가지 규칙이 있다.
⓵ React 함수 최상위에서 호출해야 한다.
⓶ 오직 React 함수 내에서만 사용해야 한다.
이번에는 React 렌더링 최적화를 도와주는 useMemo와 useCallback Hook에대해 알아볼 것이다.
useMemo
useMemo는 특정한 값을 재사용하고자 할 때 사용하는 Hook이다. 예시를 통해 알아보면
const JustComponent = () => {
const lazyFunc = (a) => {
const fibo = (n) => {
if(n <=1) return 1;
return fibo(n-1) + fibo(n-2);
}
return fibo(a);
}
let num = 50;
result = lazyFunc(50);
작동하는데 시간이 걸리는 함수가 있다. 이 함수가 어떤 컴포넌트에서 호출이 된다.
(* TMI로 위의 피보나치 50번째 수를 구하는 함수는 103.67초의 시간이 걸렸다.)
다른 상태에 의해 해당 페이지가 리렌더링 될때마다 lazyFunc라는 함수가 작동하면서 값을 렌더링한다. 그런데 이 함수는 오랜 시간이 걸리기 때문에 리렌더링에 영향을 미치고, 사용자는 해당페이지의 로딩이 오래걸린다고 생각할 수 있다.
사실 리렌더링이 되더라도 이 lazyFunc 함수의 결과값은 변하지 않기 때문에 굳이 다시 함수를 실행시킬 필요가 없다.
이러한 경우 useMemo Hook을 사용하여 해당 함수의 결과값을 기억하고 있다가 리렌더링될 때 함수를 호출하지 않고 기억해놓은 값을 렌더링 해주면된다.(메모이제이션)
num 변수에 변경이 생기지 않는 한 lazyFunc 함수는 다시 호출되지 않고 이전의 값을 기억하고 있다가 렌더링한다.
useCallback
useCallback도 useMemo와 마찬가지로 메모이제이션 개념을 적용한 Hook이다. useMemo가 값을 기억했다면, useCallback은 함수를 기억하는 방식으로 작동한다.
일반적으로 함수를 선언하면 해당 컴포넌트가 리렌더링 될때 새로운 함수가 만들어진다.
useCallback으로 함수를 위와같이 만들면 다시 함수를 만들지 않고 x,y 값이 변경될 때 useCallback 내부의 함수를 호출한다.
그러나 함수를 만드는데는 리소스가 크게 들어가지 않기 때문에 이것만 가지고는 최적화를 느낄 수 없다. 우리가 useCallback을 사용하는 이유는 함수의 참조동등성을 적용하기 위해서이다.
참조동등성
일반적으로 함수를 선언하면 리렌더링될 때마다 새로운 함수가 작성되기 때문에 리렌더링 될 때 마다 다른 참조값을 가지는, 형태와 반환값은 같지만 참조값은 다른 함수가 생성된다.
그러나 useCallback hook을 사용하면 기존에 선언해두었던 함수를 변경하지 않기 때문에 리렌더링 되더라고 같은 참조값을 가지는 함수를 계속 가진채로 있다.
이러한 참조 동등성은 useEffect 같은 hook을 사용할 때 체감할 수 있다.
input창에 숫자를 입력할 때 마다 console에 '아이템을 가져옵니다'라고 찍히는 웹이 있다.
getItem을 useCallback을 사용하지 않고 그냥 선언해도 기본 기능은 동작한다.
그런데 여기서 dark mode를 클릭해도 console에 문구가 찍히는 것을 볼 수 있다.
dark mode 버튼을 클릭하면 상태가 변경되는데 이 과정에서 페이지가 리렌더링 되면서 getItems 함수가 새롭게 작성이되고, useEffect가 실행되면서 원치않는 상황에서도 console이 찍히는것이다.
이러한 상황을 방지하기 위해 getItems 함수를 useCallback을 사용하여 선언한다.
useCallback을 사용하여 getItems를 선언하면 dark mode를 눌러 페이지가 리렌더링 되더라도 해당 함수는 새롭게 선언되지 않고 기존 참조값을 그대로 가지고 있기 때문에 useEffect 내부의 코드가 작동하지 않는것을 볼 수 있다.
Reference
- 코드스테이츠
'코드스테이츠' 카테고리의 다른 글
3/24 일일정리 React Hooks 적용하기 (0) | 2023.03.24 |
---|---|
3/23 일일정리 Custom Hook (0) | 2023.03.23 |
3/21 일일정리 웹팩 실습 (0) | 2023.03.21 |
3/20 일일정리 번들링과 웹팩 (0) | 2023.03.20 |
3/17 일일정리 애니메이션, 캔버스 (0) | 2023.03.17 |
댓글