ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 심화
    FE 2023. 7. 18. 20:02

    Virtual DOM

    ➡️ JavaScript 객체로 이루어진 가상의 DOM 트리를 사용하여 실제 DOM 조작을 최소화하고 성능을 최적화하는 기술

     

     

     

     

    DOM의 형태 : 가상 DOM은 추상화된 자바스크립트 객체의 형태를 가지고 있음

     

     

     

    Virtual DOM의 동작 과정 

    • 리액트는 상태 변경하는 작업 (e.g. 이벤트)이 일어났을 때, 가상 DOM에 저장된 현재와 이전을 비교하는데 리액트는 Diffing 알고리즘 사용하여 변경된 부분을 감지함
    • 알고리즘이 직접 감지할 수 있도록 setState와 같은 메서드를 활용해 상태 변경
    • 가상 DOM을 새로운 가상 DOM과 비교해 필요한 부분만 반영하는데 이것을 재조정 Reconciliation 이라고 함
    • 여러개의 상태 변화가 있을 경우엔 한 번에 업데이트를 해서 성능 최적화, 불필요한 리렌더링 최소화함

     

     

    Real DOM (DOM)  : Document Object Model 문서 객체 모델. (문서 객체란 브라우저가 자바스크립트와 같은 스크립팅 언어가 <html>, <head>, <body>와 같은 태그들에 접근하고 조작할 수 있도록 문서를 트리구조로 객체화 한 것)브라우저가 HTML 문서를 조작할 수 있도록 트리 구조화한 객체 모델.

     

     

     

    DOM 조작 속도가 느려지는 이유 

    • 저장된 데이터를 더 효과적으로 탐색하기 위해 사용, 빠른 자료 탐색 성능이 장점
    • 자바스크립트 같은 스크립팅 언어가 접근하고 탐색하는 속도가 빠르기 때문
    • DOM이 변경되고 업데이트가 되는 것은 브라우저의 렌더링 엔진 또한 리플로우 한다는 것을 의미
    • DOM 조작이 많아질수록 리플로우가 발생하여 업데이트 비용이 커질 수 있고 프레임드롭인 치명적인 UX문제 발생할 수 있음

     

     

     

    React가 DOM 트리 탐색하는 방법

    ➡️ 트리의 레벨 순서대로 순회 탐색. 같은 레벨(위치)끼리 비교 (너비 우선 탐색 BFS)

     

    1. 다른 타입의 DOM 엘리먼트인 경우 

    ➡️ DOM 트리는 각 HTML 태그마다 규칙이 있어 그 아래 들어가는 자식 태그가 한정적이라는 특징을 갖고 있음. 

    (e.g. <ul> 태그 밑에 <li> 태그만 와야한다던가 <p> 태그 안에 <p> 태그를 또 쓰지 못함)

    부모 태그가 바뀌면 기존의 트리를 버리고 새로운 트리를 구축하기 때문에 이전 노드들은 전부 파괴됨. 

    <div>
    	<Counter />
    </div>
    
    //부모 태그가 div에서 span으로 바뀝니다.
    <span>
    	<Counter />
    </span>

    <div>가 <span>으로 바뀌면 자식 노드인 <Counter />는 완전히 해제되며 <div> 태그 속 <Counter />는 파괴되고 <span> 태그 속 새로운 <Counter />가 실행됨

     

     

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

    ➡️ React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이트함. 실제 DOM이 아닌 가상 DOM을 조작하기 때문.

    하나의 DOM 노드를 처리한 뒤 리액트는 뒤이어서 해당 노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때마다 변경하는데 이것을 재귀적 처리라고함

    <div className="before" title="stuff" />
    
    //기존의 엘리먼트가 태그는 바뀌지 않은 채 className만 바뀌었습니다.
    <div className="after" title="stuff" />

     

     

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

    ➡️ 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐점을 찾고 첫 번째 자식노드와 두 번째 자식 노드가 일치하면 세 번째 자식 노드를 추가함. 위에서 아래로 비교하기 때문에 리스트의 처음에 엘리먼트를 삽입하게 되면 이전의 코드에 비해 훨씬 나쁜 성능을 냄. 이러한 문제를 해결하기 위해 key 속성을 사용함. (key가 없으면 비효울적으로 동작)

    <ul>
      <li>first</li>
      <li>second</li>
    </ul>
    
    //자식 엘리먼트의 끝에 새로운 자식 엘리먼트를 추가했습니다.
    <ul>
      <li>first</li>
      <li>second</li>
      <li>third</li>
    </ul>

     

     

    4. 키 (key)

    ➡️ 자식 노드가 key를 갖고 있으면 리액트는 그 key를 이용해 기존 트리의 자식과 새로운 트리의 자식이 일치하는지 확인할 수 있음. key는 보통 유니크한 값(ex. Id)를 부여해주는데 전역적으로 유일할 필요 없고 형제 엘리먼트 사이에서만 유일하면 됨. 만약 유니크한 값이 없으면 최후 수단으로 배열의 인덱스 key로 사용 가능(비효율로 동작할 수 있음)

    <ul>
      <li key="2015">Duke</li>
      <li key="2016">Villanova</li>
    </ul>
    
    //key가 2014인 자식 엘리먼트를 처음에 추가합니다.
    <ul>
      <li key="2014">Connecticut</li>
      <li key="2015">Duke</li>
      <li key="2016">Villanova</li>
    </ul>

    React Hooks

    Class Component

    ➡️ 클래스 컴포넌트는 복잡해질수록 이해하기 어렵고 로직 재사용이 어렵다는 단점이 있음. 자바스크립트의 this 키워드 동작 방식도 알기 힘들기 때문에 클래스 컴포넌트에서 Hook이라는 개념을 도입해 함수 컴포넌트로 넘어감

    class Counter extends Component {
        constructor(props) {
            super(props);
            this.state = {
                counter: 0
            }
            this.handleIncrease = this.handleIncrease.bind(this);
        }
    
        handleIncrease = () => {
            this.setState({
                counter: this.state.counter + 1
            })
        }
    
        render(){
           return (
                <div>
                    <p>You clicked {this.state.counter} times</p>
                    <button onClick={this.handleIncrease}>
                        Click me
                    </button>
                </div>
           ) 
        }
    }

     

     

    Function Component

    ➡️ 함수형 컴포넌트는 클래스형 컴포넌트보다 직관적이며 보기쉬움. Counter 컴포넌트에서 숫자를 올리기 위해 상태값을 저장하고 사용할 수 있게 해주는 useState()는 Hook임. Hook을 호출해 함수 컴포넌트 안에 state를 추가한 형태이며 state는 리렌더링 되어도 그대로 유지함(때에 따라 State Hook 여러개 사용 가능)

    function Counter () {
        const [counter, setCounter] = useState(0);
    
        const handleIncrease = () => {
            setCounter(counter + 1)
        }
    
        return (
            <div>
                <p>You clicked {counter} times</p>
                <button onClick={handleIncrease}>
                    Click me
                </button>
            </div>
        )
    }

     

     

    Hook 

    ➡️ class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해줌. 함수형 컴포넌트에서 상태값 및 다른 여러 기능을 사용하기 편리하게 해주는 메서드(클래스형 컴포넌트에서는 동작하지 않음)

     

    Hook 사용 규칙

    1. 리액트 함수의 최상위에서만 호출 : 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 예상한대로 동작하지 않을 수 있음

    2. 오직 리액트 함수 내에서만 사용 : 리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 자바스크립트 함수 안에서 호출해서는 안 됨

     

     

    useMemo

    ➡️ 특정 값(value)을 재사용하고자 할 때 사용하는 Hook

    /* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
    import { useMemo } from "react";
    
    function Calculator({value}){
    
    	const result = useMemo(() => calculate(value), [value]);
    
    	return <>
          <div>
    					{result}
          </div>
      </>;
    }

    value는 일종의 값으로 렌더링 할 때마다 값이 계속 바뀌는게 아니라고 할 때, 저장해뒀다가 다시 꺼내 쓸 수 있으면 calculate 함수를 호출할 필요가 없음. 이럴 때 useMemo를 호출하여 calculate를 감싸주면 이전에 구축된 렌더링과 새로 구축된 렌더링을 비교해 value 값이 동일할 경우 이전 렌더링의 value 값을 재사용할 수 있음. 메모이제이션(Memoization)과 긴밀한 관계가 있음

     

     

    Memoization : 알고리즘에서 자주 나옴. 기존에 수행한 연산의 결과값을 메모리에 저장해서 동일한 입력이 들어오면 재활용하는 프로그래밍 기법. 앱의 성능을 최적화할 수 있음

     

     

     

    useCallback

    ➡️ useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook. 함수의 재사용을 위해 사용함

    /* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
    import React, { useCallback } from "react";
    
    function Calculator({x, y}){
    
    	const add = useCallback(() => x + y, [x, y]);
    
    	return <>
          <div>
    					{add()}
          </div>
      </>;
    }

    해당 컴포넌트가 리렌더링 되더라도 x와 y가 바뀌지 않는다고 했을 때, 함수도 메모리 어딘가에 저장해 뒀다가 다시 꺼내서 쓸 수 있다. useCallback Hook을 사용하면 함수가 의존하는 값들이 바뀌지 않는한 기존 함수를 계속해서 반환하고 x와 y 값이 동일하면 다음 렌더링 때 이 함수를 다시 사용.

    (메모리 어딘가에 함수를 꺼내서 호출하는 Hook이라 자식 컴포넌트의 props로 함수를 전달해줄 때 사용하기 좋음)

     

     

    useCallback과 참조 동등성

    ➡️ useCallback은 참조 동등성에 의존하는데 기본적으로 자바스크립트 문법을 따라감.(자바스크립트에서 함수는 객체) 객체를 메모리에 저장할 때 값의 주소를 저장하기 때문에 반환하는 값이 같아도 일치연산자로 false가 출력됨

    function doubleFactory(){
        return (a) => 2 * a;
    }
      
    const double1 = doubleFactory();
    const double2 = doubleFactory();
      
    double1(8); // 16
    double2(8); // 16
      
    double1 === double2;  // false
    double1 === double1;  // true

    double1과 2는 같은 함수를 할당했어도 주소값이 다르기 때문에 같지 않음. 리액트도 리렌더링 시 함수를 새로 만들어서 호출하는데 호출된 함수와 기존 함수는 같은 함수가 아님. useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용하는 것과 같음

    'FE' 카테고리의 다른 글

    React Custom Component  (0) 2023.07.20
    [React] 심화  (0) 2023.07.19
    [솔로 프로젝트]  (0) 2023.07.17
    [솔로 프로젝트]  (0) 2023.07.14
    프로젝트 요구사항 분석  (0) 2023.07.13
Designed by Tistory.