티스토리 뷰

Real DOM

DOM. 실제 돔. 가상 돔과 구분하기 위해 Real DOM이라 명칭함

브라우저(JavaScript 같은 스크립팅 언어)가 태그에 접근하여 조작하기 위해 태그들을 트리 구조로 만든 객체 모델

 

DOM의 리렌더링 과정

DOM은 UI가 변경될 때마다 업데이트하는데, DOM 조작이 잦다면

DOM 렌더링은 브라우저의 파워(구동 능력)에 의존하기 때문에 성능이 안좋아지고 조작속도가 느려지게 됨

 

트리 구조는 저장된 데이터를 효과적으로 탐색할 수 있게하기 때문에 탐색 속도도 빠르고 변경/업데이트 속도도 빠르지만

요소가 변경/업데이트되어 DOM 트리가 재구축되면

브라우저 렌더링 엔진에서 리플로우, 리페인트(Reflow, Repaint)가 발생하며 재연산으로 인해 속도가 느려지게 됨

DOM 트리

 

Virtual DOM

React의 가상 돔. 실제 돔의 사본

실제 돔과 마찬가지로 HTML 문서 객체를 기반으로 함

 

기존의 가상 돔과, 요소의 상태가 변경되어 만들어진 새로운 가상 돔을 비교하여 브라우저의 실제 돔을 변경

-> 실제 돔은 최소한의 작업을 하게 되며, 부분적으로 리렌더링하여

빠른 렌더링이 가능하게 됨(브라우저 파워를 덜 쓰고 업데이트 비용일 줄게 됨)

Virtual DOM 형태(JavaScript 형태)

Virtual DOM JavaScript 객체로 표현

const vDom = {
  tagName: "html",
  children: [
    { tagName: "head" },
    { tagName: "body",
      children: [
        {
          tagName: "ul",
          attributes: { "class": "list"},
          children: [
          {
            tagName: "li",
            attributes: { "class": "list_item" },
            textContent: "List item"
          }
          ]
        }
      ]
    }
  ]
}

 

React Diffing Algorithm

기존 가상 돔과 변경 사항을 적용한 새로운 가상 돔을 비교할 때

기존 알고리즘은 O(n^3)의 복잡도를 가짐

 

개선하기 위해 아래 두 조건을 가정하고 새로운 알고리즘 구현

① 서로 다른 두 요소는 다른 트리를 구축할 것임

② key 프로퍼티로 여러번 렌더링해도 변경되지 말아야하는 자식 요소 알 수 있을 것임

 

1. 같은 레벨끼리 비교(BFS 너비우선탐색)

 

2. 다른 타입 DOM 엘리먼트인 경우 새로운 트리를 구축함

부모 요소가 기존의 요소가 아니라면 기존 요소가 가지고 있던 트리를 버리고 새 트리를 구축함(조건①)

-> 같은 자식 요소를 갖더라도 기존의 자식 요소(+ state)는 파괴되고 새로운 자식 요소가 연결됨

<div>
  <Something />
</div>

<span>
  <Something />
</span>

 

3. 같은 타입 DOM 엘리먼트인 경우 최소한의 변경사항만 업데이트

div 요소의 className이 다르고, className에 적용된 스타일 요소 중 color만 다르다면, color 속성만 변경

<div className="before" title="stuff" />
<div className="after" title="stuff" />

<div style={{color: 'red', fontWeight: 'bold"}} title="stuff" />
<div style={{color: 'green', fontWeight: 'bold"}} title="stuff" />

 

하나의 DOM 노드를 처리한 뒤 자식 요소를 순회하며 차이가 발견될 때마다 변경(재귀적으로 처리)

 

4. 자식 엘리먼트의 재귀적 처리

React는 순차적으로 비교하기 때문에 새로운 요소가 맨 앞에 추가되는 경우

기존 요소 모두가 달라졌다고 인식해 전부 새로 렌더링함 -> 비효율적임

 

요소마다 key를 부여하여 기존 트리의 자식과 새 트리의 자식이 일치하는지 확인 후 처리해줌(조건②)

key: 형제들 사이에 유일한 값을 부여. 전역적으로 유일할 필요 없음

-> 인덱스를 키로 부여한다면, 후에 배열이 다르게 정렬될 경우 요소가 전부 변경됐다고 인식하여 비효율적으로 동작할 수 있으니 주의

 

 

React Hook

함수형 컴포넌트를 더 직관적으로 작업할 수 있게 도와주는 기능(React 16.8 버전부터 추가됨)

class를 사용하지 않고도 state와 다른 React 기능을 사용할 수 있게 해줌

클래스형 컴포넌트에서는 동작하지 않음

 

클래스형 컴포넌트

코드가 복잡해지면 이해하기 어려워짐

컴포넌트 사이에서 상태 로직을 재사용하기 어려움

this 키워드 동작 방식에 이해가 필요함

 

=> 직관적이고 보기 쉬운 함수형 컴포넌트로 넘어가게 됨

함수형 컴포넌트는 상태값을 사용하거나 최적화할 수 있는 기능이 미진했는데 그를 보완하기 위해 Hook이 도입됨

 

Hook 사용 규칙

1. 리액트 함수 최상위에서만 호출해야 함

컴포넌트 내에서 여러 가지의 Hook이 사용될 수 있는데 React는 Hook이 호출되는 순서대로 저장해놓음

-> 반복문, 조건문, 중첩된 함수 내에서 호출하면

Hook이 항상 호출되지 않기 때문에 순서대로 저장하기 어려워지고 버그를 초래할 수 있음

 

2. 리액트 함수 내에서만 사용해야 함

Hook은 리액트 함수형 컴포넌트를 위해 만들어진 메서드기 때문에 일반 JavaScript에서 호출할 경우 동작하지 않음

예외: Custom Hook

 

 

렌더링 최적화를 위한 useMemo, useCallback

useMemo

특정 값을 재사용하고 싶을 때 사용하는 Hook

Props로 받은 값을 인자로 함수를 실행하는데 함수의 연산이 복잡하여 시간이 소요될 경우 사용

 

기존: 값이 바뀌지 않았는데도 리렌더링이 일어난다면 항상 함수를 실행함

function Calculator({ value }){
  const result = calculate(value); // 렌더링 시마다 항상 호출

  return 
    <>
      <div>
        {result}
      </div>
    </>;
}

 

useMemo 사용: value를 기억하여 value가 바뀔 때만 함수 실행

import ( useMemo } from "react";
function Calculator({ value }){
  const result = useMemo(() => calculate(value), [value]);

  return 
    <>
      <div>
        {result}
      </div>
    </>;
}

 

useMemo는 Memoization 개념을 활용하여 복잡한 연산의 중복을 피하고 앱의 성능을 최적화함

 

Memoization

기존에 수행한 연산의 결과값을 메모리에 저장해두고 동일한 입력이 들어오면 재사용하는 프로그래밍 기법

 

useCallback

Memoization 개념을 이용하여 함수를 재사용

메모리에 저장된 함수를 꺼내서 호출하는 Hook이기때문에 호출하지 않는 useMemo와 달리 호출을 계속 함

-> 함수를 반복 생성하지 않기 위한 용도로만 사용하는 것은 의미가 없거나 오히려 손해일 수 있음

 

함수를 Props로 내려주는 경우 사용

함수는 JavaScript 객체이기 때문에 값이 아닌 주소를 저장함

함수를 선언하는 컴포넌트가 렌더링될 때마다 변수에 새로운 주소가 할당됨

함수를 Props로 받는 자식 컴포넌트는 계속해서 새로운 주소를 받기 때문에 불필요하게 리렌더링됨

-> useCallback으로 감싼 함수를 Props로 내려 불필요한 렌더링을 막아 예상치 못한 성능 저하를 막을 수 있음

import React, { useCallback } from "react";

function Calculator({ x, y }){
  const add = useCallback(() => x + y, [x, y]);

  return 
    <>
      <div>
        {add()}
      </div>
    </>;
}

 

 

Custom Hook

사용자 정의 훅. use로 시작하는 JavaScript 함수

반복되는 로직(상태 관련 로직)을 Hook으로 만들어 재사용할 수 있음

 

장점

① 상태 관리 로직 재사용 가능

② 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있음

③ 함수형이기 때문에 보다 명료함

 

규칙

① Custom Hook을 정의할 때 함수 이름 앞에 use- 붙이기

② 프로젝트 내부의 hooks 디렉토리에 Custom Hook 위치시키기

③ 조건부함수가 아니어야 됨(return값이 조건부가 아니어야함)

 

Custom Hook(JavaScript 함수) 내부에서 React 내장 훅(useState, useEffect 등)을 이용할 수 있음

 

Custom Hook을 이용하는 컴포넌트들은 같은 로직을 공유할 뿐 같은 state를 공유하는 것이 아님

state는 Custom Hook을 사용하는 컴포넌트 내에서 독립적으로 정의되어 있음

 

예시 1. 여러 url을 fetch하는 경우

const useFetch = ( initialUrl:string ) => {
  const [url, setUrl] = useState(initialUrl);
  const [value, setValue] = useState('');

  const fetchData = () => axios.get(url).then(({data}) => setValue(data));	

  useEffect(() => {
    fetchData();
  },[url]);

  return [value];
};

export default useFetch;

 

예시 2. 여러 input에 의한 상태 변경이 필요한 경우

import { useState, useCallback } from 'react';

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(form => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

 

 

 

 

 

2023.01.20

코스 S4U4 React Hook 진행중

댓글
공지사항