[S4U4] React 심화

Yeong·2023년 3월 22일
0

React

목록 보기
1/16

1. Virtual DOM

1-1. 리액트와 Virtual DOM

Real DOM (DOM)

DOM은 브라우저가 HTML 문서를 조작할 수 있도록 트리 구조화한 객체 모델이다.
querySelector, addEventListener와 같은 DOM API를 통해 문서의 요소들을 조작할 수 있다.

Virtual DOM

리액트의 Virtual DOM 실제 DOM의 복사본이며, 자바스크립트 객체 형태로 저장 되어 있다. 그러므로 실제돔의 복사본이지만 실제돔과 비교해 훨씬 가볍다.
다만 가상돔은 말그래도 가상이기 때문에 실제돔처럼 화면의 내용을 직접 조작할 수 없다.

Virtual DOM이 나오게 된 배경

돔은 트리 구조이므로 저장된 데이터를 탐색하는 데 효과적이나 트리 구조이기 때문에 돔이 변경되거나 업데이트가 되면 변경된 요소뿐만 아니라 그 요소의 자식요소까지 재구축하게 된다.
Virtual DOM은 이러한 단점을 보완하기 위해 등장하게 되었다.

Virtual DOM의 동작 과정


1. 가상 DOM은 실제 DOM과 동기화되어, 상태가 변경될 때마다 가상 DOM을 새로 생성한다.
2. 실제 돔이 변경되기 이전에 리액트는 상태가 변경되기 전 가상돔과 변경 된 후 새로 생성된 가상돔을 비교해서 정확히 어떤 부분이 바귀었는지 비교하여 파악한다. 이 과정을 diffing이라 부른다.
3. diffing 후에 변경이 필요한 부분만 실제 DOM에 반영하여 업데이트한다. 이것을 Reconciliation, 즉 재조정이라고 한다.
이 과정에서 여러 개의 상태 변화가 있을 경우 이를 일일이 수행하지 않고 일괄적으로 한번에 업데이트(Batch Update)한다. 이를 통해 성능을 최적화하고 불필요한 리렌더링을 최소화할 수 있다.

1-2. React Diffing Algorithm

기본 가상돔과 변경된 새로운 가상돔을 비교할 때, 시간 복잡도를 낮추기 위해 두 가지의 가정을 가지는 휴리스틱 알고리즘(Heuristic Algorithm)을 적용한다.

두 가지 가정

  1. 각기 서로 다른 두 요소는 다른 트리를 구축할 것이다.
  2. 개발자가 제공하는 key 프로퍼티를 가지고, 여러 번 렌더링을 거쳐도 변경되지 말아야 하는 자식 요소가 무엇인지 알아낼 수 있을 것이다.

휴리스틱 알고리즘
휴리스틱 또는 발견법이란 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나, 체계적이면서 합리적인 판단이 굳이 필요하지 않은 상황에서 사람들이 빠르게 사용할 수 있게 보다 용이하게 구성된 간편추론의 방법이다. -위키백과

React가 DOM 트리를 탐색하는 방법

React는 기존의 가상 DOM 트리와 새롭게 변경된 가상 DOM 트리를 비교할 때, 트리의 레벨 순서대로 순회하는 방식으로 탐색한다.

다른 타입의 DOM 엘리먼트로 변경되는 경우

부모 태그가 다른 타입의 돔 엘리먼트로 달라진다면 React는 이전 트리를 버리고 새로운 트리를 구축한다.
컴포넌트는 완전히 해제(Unmount)되어버리기 때문에 자식컴포넌트가 갖고 있던 기존의 state 또한 파괴된다.

같은 타입의 DOM 엘리먼트인 경우

반대로 부모이 타입이 바뀌지 않는다면 React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이트한다.

자식 엘리먼트 변경

React는 기존 자식 노드와 새로운 자식 노드를 비교할 때 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐 점을 찾는다.
그래서 만약 새로운 자식이 기존 자식을 앞에 만들어 졌다면 기존의 자식 노드를 유지시켜도 된다는 걸 모르고 전부 버리고 새롭게 렌더링한다.

하지만 자식 노드들이 이 key를 갖고 있다면, React는 그 key를 이용해 기존 트리의 자식과 새로운 트리의 자식이 일치하는지 아닌지 확인할 수 있어 새로운 자식만 추가해 줄 수 있다.

2. React Hooks

2-1. Hook

Hook은 React 16.8에 새로 추가된 기능입니다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해줍니다.

Hook은 다르게 말하면 함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드를 의미한다.
Hook은 class가 아닌 function으로만 React를 사용할 수 있게 해주는 것이기 때문에 클래스형 컴포넌트에서는 동작하지 않는다.

Hook 사용 규칙

  1. 리액트 함수의 최상위에서만 호출해야 한다.
  2. 오직 리액트 함수 내에서만 사용되어야 한다.

2-2. useMemo

useMemo은 특정 값(value)를 재사용하고자 할 때 사용하는 Hook이다.

Memoization

메모이제이션은 알고리즘에서 자주 나오는 개념으로 기존에 연산한 값을 메모리에 저장해 두고, 동일한 입력이 들어 올때 똑같은 연산을 반복하지 않고 저장 해둔 값을 재활용하는 기법이다. 이를 통해 앱의 성능을 최적화할 수 있다.
useMemo Hook을 호출한다면 이런 로직을 직접 구현하지 않고도 간단하게 사용할 수 있다.

사용 예시

import React, { useState, useMemo } from "react"; // import 해오기
import "./styles.css";
import { add } from "./add";

export default function App() {
  const [name, setName] = useState("");
  const [val1, setVal1] = useState(0);
  const [val2, setVal2] = useState(0);
  const answer = useMemo(() => add(val1, val2), [val1, val2]); 
  // 인자로 생성함수와 저장할 값 배열로
 // 만약 useMemo 없이 const answer = add(val1, val2)일 경우
// name이 변경되어 리렌더링된다면 answer의 값이 변함이 없어도 다시 계산해야한다.

  return (
    <div>
      <input
        className="name-input"
        placeholder="이름을 입력해주세요"
        value={name}
        type="text"
        onChange={(e) => setName(e.target.value)}
      />
      <input
        className="value-input"
        placeholder="숫자를 입력해주세요"
        value={val1}
        type="number"
        onChange={(e) => setVal1(Number(e.target.value))}
      />
      <input
        className="value-input"
        placeholder="숫자를 입력해주세요"
        value={val2}
        type="number"
        onChange={(e) => setVal2(Number(e.target.value))}
      />
      <div>{answer}</div>
    </div>
  );
}

2-3. useCallback

useCallback 또한 useMemo처럼 렌더링 최적화를 위한 Hook이다.
useMemo와 차이점은 useMemo는 값의 재사용을 위해 사용하는 Hook이라면, useCallback은 함수의 재사용을 위해 사용하는 Hook이다.

useCallback과 참조 동등성

JavaScript에서 함수는 객체이다. 객체 참조형 데이터이므로 값을 저장하는 것이 아니라 값의 주소값을 저장한다. 즉, 함수의 반환하는 값이 같아도 주소값이 다르면 일치연산자로 비교했을 때 false가 출력된다.

React 또한 같다. React는 리렌더링 시 함수를 새로 만들어서 호출하면 새로 호출된 함수는 기존의 함수와 반환값이 같더라도 같은 함수가 아니다. 그러나 useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다. 따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.

사용 예시

import { useState, useCallback } from "react";
import "./styles.css";
import List from "./List";

export default function App() {
  const [input, setInput] = useState(1);
  const [light, setLight] = useState(true);

  const theme = {
    backgroundColor: light ? "White" : "grey",
    color: light ? "grey" : "white"
  };

  const getItems = useCallback(() => 
    [input + 10, input + 100], [input]);
 // const getItems = () => {return [input + 10, input + 100];} 이라면
 // 테마가 변경되어 리렌더링될 때 새로 함수도 실행된다. 
// 하지만 useCallback으로 함수를 저장해 두면 새로 함수를 실행하지 않고 저장해둔 함수를 재활용하게 된다.

  const handleChange = (event) => {
    if (Number(event.target.value)) {
      setInput(Number(event.target.value));
    }
  };

  return (
    <>
      <div style={theme} className="wall-paper">
        <input
          type="number"
          className="input"
          value={input}
          onChange={handleChange}
        />
        <button
          className={(light ? "light" : "dark") + " button"}
          onClick={() => setLight((prevLight) => !prevLight)}
        >
          {light ? "dark mode" : "light mode"}
        </button>
        <List getItems={getItems} />
      </div>
    </>
  );
}
profile
긍정적으로~✍️(◔◡◔)

0개의 댓글