장경은
Study

[2장] 리액트 핵심 요소 깊게 살펴보기

JSX란?

가상 DOM과 리액트 파이버

  • DOM과 브라우저 렌더링 과정

    스크린샷 2024-01-27 오후 6 57 02
  • 가상 DOM 방식은 일반적인 방식보다 무조건 빠른 것이 아니라 대부분의 상황에서 웬만한 애플리케이션을 만들 수 있을 정도로 충분히 빠르다.

  • 재조정(reconciliation)

    • 리액트에서 어떤 부분을 새롭게 렌더링해야 하는지 가상 DOM과 실제 DOM을 비교하는 작업(알고리즘)

리액트 파이버

  • 리액트 파이버는 리액트에서 관리하는 평범한 자바스크립트 객체로, 가상 DOM과 렌더링 과정 최적화를 가능하게 해준다.

  • 리액트 웹 애플리케이션에서 발생하는 애니메이션, 레이아웃, 그리고 사용자 인터랙션에 올바른 결과물을 만드는 반응성 문제를 해결하는 것이 목표

  • 모든 과정이 비동기로 일어난다. -> 과거에는 스택으로 이루어졌지만, 동기적으로 이루어지다보니 리액트의 비효율성 초래

  • 파이버의 작업 단계

    1. 렌더 단계: 렌더 단계에서 리액트는 사용자에게 노출되지 않는 모든 비동기 작업을 수행한다. 그리고 이 단계에서 앞서 언급한 파이버의 작업, 우선순위를 지정하거나 중지시키거나 버리는 등의 작업이 일어난다.
    2. 커밋 단계: 커밋 단계에서는 앞서 언급한 것처럼 DOM에 실제 변경 사항을 반영하기 위한 작업, commitWork()가 실행되는데, 이 과정은 앞서와 다르게 동기식으로 일어나고 중단될 수도 없다.
  • 파이버는 컴포넌트가 최초로 마운트되는 시점에 생성되어 이후에는 가급적이면 재사용된다. (가급적 새로운 파이버를 생성하지 않는다.)

  • 리액트 파이버의 주요 속성

    • tag
    • stateNode
    • child, sibling, return
    • index
    • pendingProps
    • memoizedProps
    • updateQueue
    • memoizedState
    • alternate
  • 리액트 파이버 트리

    • 파이버 트리는 사실 리액트 내부에서 두 개가 존재한다. 하나는 현재 모습을 담은 파이버 트리이고, 다른 하나는 작업 중인 상태를 나타내는 workInProgress 트리다.
    • 리액트 파이버의 작업이 끝나면 리액트는 단순히 포인터만 변경해 workInProgress 트리를 현재 트리로 바꿔버린다. 이러한 기술을 더블 버퍼링이라고 한다.
    • 미처 다 그리지 못한 모습을 노출시키지 않기 위해(불완전한 트리를 보여주지 않기 위해) 더블 버퍼링 기법을 쓴다. 이를 위해 트리가 두 개 존재하고 커밋 단계에서 수행된다.
    • 현재는 비동기식으로 처리되므로 우선순위가 높은 다른 업데이트가 오면 현재 업데이트 작업을 일시 중단하거나 새롭게 만들거나, 폐기할 수도 있다.
  • 리액트 파이버는 리액트 네이티브와 같은 브라우저가 아닌 환경에서도 사용할 수 있기 때문에 파이버와 가상 DOM은 동일한 개념이 아니다.

  • 가상 DOM과 리액트의 핵심은 브라우저의 DOM을 더욱 빠르게 그리고 반영하는 것이 아니라 바로 값으로 UI를 표현하는 것이다.

  • 화면에 표시되는 UI를 자바스크립트의 문자열, 배열 등과 마찬가지로 값으로 관리하고 이러한 흐름을 효율적으로 관리하기 위한 메커니즘이 바로 리액트의 핵심이다.

클래스 컴포넌트와 함수 컴포넌트

  • 클래스 컴포넌트
    • 생명주기 메서드 중 getDerivedStateFromError(), componentDidCatch(), getSnapshotBeforeUpdate()는 아직 리액트 훅으로 구현돼 있지 않기 때문에 이 세 가지 메서드가 필요한 경우가 있다면 반드시 클래스 컴포넌트를 사용해야 한다.
  • 함수 컴포넌트
    • 함수 컴포넌트는 useEffect 훅을 사용해 앞서 언급했던 생명주기 메서드인 componentDidMount, componentDidUpdate, componentWillUnmount를 비슷하게 구현할 수 있다. 이는 비슷할 뿐이지 똑같은 것은 아니다.

렌더링은 어떻게 일어나는가?

  • 리액트에서의 렌더링이란 리액트 애플리케이션 트리 안에 있는 모든 컴포넌트들이 현재 자신들이 가지고 있는 props와 state의 값을 기반으로 어떻게 UI를 구성하고 이를 바탕으로 어떤 DOM 결과를 브라우저에 제공할 것인지 계산하는 일련의 과정을 의미한다.
  • 리액트의 렌더링이 일어나는 이유 [p.173]
    • 리렌더링이 발생하면 current 트리와 workInProgress 트리 사이에서 어떠한 컴포넌트가 변경이 있었는지 구별해야 하는데, 이 두 트리 사이에서 같은 컴포넌트인지를 구별하는 값이 바로 key다.
    • 즉, key의 변화는 리렌더링을 야기한다.
    • 부모 컴포넌트가 리렌더링된다면 자식 컴포넌트도 무조건 리렌더링이 일어난다.
  • useState 등으로 관리되지 않는 단순한 변수(let, const)는 제아무리 변경된다 하더라도 리렌더링을 발생시키지 않아 변경된 값을 렌더링된 DOM에서 확인할 수 없다.
  • 일반적으로 렌더링 결과물은 JSX 문법으로 구성돼 있고, 이것이 자바스크립트로 컴파일되면서 React.createElement()를 호출하는 구문으로 변환된다. 스크린샷 2024-01-27 오후 7 36 08

리액트의 렌더링

  • 렌더 단계와 커밋 단계라는 총 두 단계로 분리되어 실행된다.
  • 렌더 단계
  • 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업
  • 변경이 필요한 컴포넌트를 체크하는 단계
  • 여기서 비교하는 것은 크게 세 가지로, type, props, key이다.
  • 커밋 단계
    • 렌더 단계의 변경사항을 실제 DOM에 적용해 사용자에게 보여주는 과정
  • 리액트의 렌더링이 일어난다고 해서 무조건 DOM 업데이트가 일어나는 것은 아니다. -> 렌더링을 수행했으나 커밋 단계까지 갈 필요가 없다면, 즉 변경 사항을 계산했는데 아무런 변경 사항이 감지되지 않는다면 이 커밋 단계는 생략될 수 있다.
  • 의도된 우선순위로 컴포넌트를 렌더링해 최적화할 수 있는 비동기 렌더링, 이른바 동시성 렌더링이 리액트 18에서 도입되었다. -> 이를 통해 브라우저의 동기 작업을 차단하지 않고 백그라운드에서 새로운 리액트 트리를 준비할 수도 있으므로 사용자는 더욱 매끄러운 사용자 경험을 누릴 수 있다.

컴포넌트와 함수의 무거운 연산을 기억해 두는 메모이제이션

  • 주장 1: 섣부른 최적화는 독이다, 꼭 필요한 곳에만 메모이제이션을 추가하자
    • 메모이제이션에도 비용이 든다. 값을 비교하고 렌더링 또는 재계산이 필요한지 확인하는 작업, 그리고 이전에 결과물을 저장해 두었다가 다시 꺼내와야 한다는 두 가지 비용이 있다.
    • 메모이제이션을 활요한 최적화는 신중을 기해야 한다.
  • 주장 2: 렌더링 과정의 비용은 비싸다, 모조리 메모이제이션해 버리자.
    • memo를 하지 않았을 때 발생할 수 있는 잠재적인 위험 비용이 더 크다
  • 항상 무엇이 더 저렴한지를 매번 계산하기 보다 무조건 메모이제이션하는 방법을 고민해보자.
  • 메모이제이션은 하지 않는 것보다 메모이제이션 했을 때 더 많은 이점을 누릴 수 있다.
  • 저자 입장
    • 리액트를 배우고 있는 상황 -> 깊이 이해하면서 시간 투자, 주장 1
    • 현업에서 일하고 있는 상황 -> 시간 투자를 적게 하는 방법인, 주장 2 / 성능에 대해서 지속적으로 모니터링하고 관찰하는 것보다 섣부른 메모이제이션 최적화가 주는 이점이 더 클 수 있다.