[React] 리액트 Fiber는 왜 등장하게 되었을까? (feat. 리액트 동작 가이드)

kimi·2024년 5월 25일
2
post-thumbnail

React는 현대 웹 개발에서 가장 인기 있는 라이브러리 중 하나입니다.
하지만 리액트 초기 버전에서는 (특히 대규모 애플리케이션에서) 랜더링과 관련한 성능 문제가 이슈가 되었고, 이러한 문제들을 해결하기 위해 리액트 팀은 16버전부터 Fiber라는 아키텍처를 도입하게 되었습니다.

1️⃣ 리액트는 어떻게 동작할까?

먼저 리액트가 어떻게 상태 변화에 따라 UI를 업데이트하는지 알아보며,
기존 방식에 어떤 한계점이 있었는지, 이를 해결하기 위해 Fiber가 어떻게 설계되었는지 알아보도록 하겠습니다.

리액트는 컴포넌트를 상태(state)와 속성(props)을 통해 렌더링합니다.

상태는 컴포넌트 내부에서 관리하는 데이터이고, 속성은 부모 컴포넌트에서 자식 컴포넌트로 전달되는 데이터입니다.

리액트는 가상 DOM을 사용하여 UI를 효율적으로 업데이트합니다.
상태가 변경되면 리액트는 새로운 가상 DOM을 생성하고, 이를 이전의 가상 DOM과 비교하여 변경된 부분만 실제 DOM에 적용합니다.

이를 재조정(Reconciliation)이라고 하며, 이 과정에서 Diffing Algorithm이 사용됩니다.

✻ 위의 그림처럼 필요한 변경 사항(컴포넌트/노드)만 업데이트하여 성능이 향상됩니다.
✻ 불필요한 리렌더링을 방지할 수 있습니다.

Reconciliation & Diffing Algorithm
리액트 공식문서

🖇 Reconciliation vs Rendering

Reconciliation(재조정)과 Rendering(렌더링)은 리액트의 UI 업데이트 과정에서 서로 다른 역할을 합니다.

Rendering은 위에서 언급한 Reconciliation 과정에서 결정된 변경 사항을 실제로 DOM에 반영하는 과정입니다.
이는 UI를 실제로 업데이트하는 작업을 의미합니다.

Reconciliation과 Rendering의 분리는 리액트가 다양한 렌더링 환경에서 일관된 성능과 유연성을 제공할 수 있게 합니다.

📎 React DOM

  • 웹 애플리케이션에서 상태가 변경되면, Reconciler는 새로운 가상 DOM 트리를 생성하고 이전 트리와 비교하여 변경된 부분을 찾습니다.
  • React DOM Renderer는 이러한 변경 사항을 실제 브라우저의 DOM에 반영합니다.

📎 React Native

  • 모바일 애플리케이션에서 상태가 변경되면, 동일한 Reconciler가 새로운 가상 트리를 생성하고 변경된 부분을 찾습니다.
  • React Native Renderer는 이 변경 사항을 네이티브 뷰(예: iOS의 UIView, Android의 View)에 반영합니다.

React DOM과 React Native는 동일한 Reconciler를 사용하여 상태 변화와 업데이트를 계산하며, 각자의 Renderer를 통해 웹과 네이티브 환경에서 각각 UI를 업데이트합니다.

2️⃣ Stack Reconciliation : Fiber이전의 Reconciliation

기존의 방식인 Stack Reconciliation에서는 다음과 같은 방식으로 동작했습니다.

  1. 동기적 처리: 전체 컴포넌트 트리를 한 번에 비교하고 업데이트합니다.
  2. 깊이 우선 탐색(DFS): 트리의 모든 노드를 순회하면서 상태 변화에 따른 차이점을 찾습니다.

☠️ 여기서 문제점이 발생합니다!

동기적 처리 방식에서는 사용자 입력에 대한 응답이 늦어지거나, 큰 컴포넌트 트리를 업데이트할 때 애니메이션이 끊기는 현상 등이 발생할 수 있습니다.

또한, 깊이 우선 탐색은 모든 노드를 순차적으로 비교해야하므로, 실제로 변경된 부분이 적더라도 많은 노드를 비교해야 합니다.

stack reconciliation은 간단하고 직관적이지만, 성능상의 한계가 있었습니다.

Fiber는 이러한 한계를 극복하기 위해 도입된 새로운 아키텍처입니다.

3️⃣ Fiber의 등장 : 비동기적 처리, 우선순위 기반 업데이트

Fiber의 주요 목표는 React가 스케줄링을 활용할 수 있도록 하는 것입니다.
이는, 렌더링과 업데이트 과정을 더욱 유연하고 효율적으로 만드는 것을 뜻합니다.

Fiber 공식문서에서는 다음과 같은 일들을 fiber가 해야한다고 소개합니다.

  • 일을 잠시 멈추고 나중에 다시 돌아오기
  • 다른 유형의 일에 우선권 부여하기
  • 이전에 완성한 작업을 재사용하기
  • 더 이상 필요하지 않은 작업을 중단하기

다음은 Fiber의 주요 개선점들입니다.

  1. 렌더링 작업을 작은 단위로 나누어 비동기적으로 처리합니다.
  2. 우선순위 기반 업데이트를 통해 중요한 작업을 먼저 처리합니다.
  3. 트리를 비교하고 업데이트하는 과정을 최적화하여 불필요한 계산과 렌더링을 줄입니다.

Fiber는 작업의 단위로, 각 컴포넌트의 랜더링 정보를 담고있습니다.

React 앱을 렌더링하는 것은 다른 함수들을 호출하는 body를 가진 함수를 호출하는 것과 유사합니다.

컴퓨터가 일반적으로 프로그램 실행을 추적하는 방법은 콜 스택을 사용하는 것입니다.
함수가 실행되면 새로운 스택 프레임이 스택에 추가됩니다.
(stack frame : 해당 함수에 의해 수행되는 작업)

UI를 다룰 때 한 번에 너무 많은 작업을 실행하면, 애니메이션이 프레임을 떨어뜨리고 고르지 않게 보일 수 있다는 문제가 있습니다.

그렇다면 UI 렌더링에 최적화되도록 Call Stack의 동작을 사용자 지정할 수 있다면, Call Stack과 Stack Frame을 조작할 수 있다면 좋지 않을까요?

Fiber는 리액트 컴포넌트에 특화된 Stack을 재구성합니다.
하나의 단일 Fiber를 Virtual Stack Frame으로 간주할 수 있습니다.

Stack을 재구성하면 Stack Frame 메모리에 보관하여 원하는 경우 언제든지 실행할 수 있다는 이점을 얻을 수 있습니다.
이는 스케줄링 목표를 달성하는 데 매우 중요합니다.

스케줄링 외에도 스택 프레임을 수동으로 처리하게되면, concurrency 및 error boundary와 같은 기능들에 대한 잠재력을 확보할 수 있습니다.
(그러나 이러한 저수준의 작업은 보통 프로그래밍 언어의 추상화 수준을 낮추고 코드의 가독성과 유지보수성을 저하시킬 수 있습니다.
따라서 이러한 기술을 사용할 때는 주의 깊게 고려해야 합니다.)

⚫️ Fiber의 구조

Fiber에 속하는 몇 가지 중요한 필드들을 간단하게 알아보겠습니다.

type & key

  • type: 해당 컴포넌트를 설명합니다. (함수 컴포넌트인지, 클래스 컴포넌트인지, 혹은 호스트 컴포넌트인지)
  • key: 파이버의 재사용 가능 여부를 결정하기 위해 사용됩니다.

child & sibling

  • child: 현재 파이버가 렌더링하는 컴포넌트의 자식 컴포넌트에 해당합니다.
  • sibling: 같은 부모 컴포넌트에 속하는 다른 자식 컴포넌트를 나타냅니다.

return

현재 파이버의 렌더링이 완료된 후 반환해야 하는 다음 파이버를 가리킵니다.

pendingProps & memoizedProps

props는 함수의 인수와 유사하게 동작합니다.
두 속성이 동일한 경우, 이전 작업의 결과를 재사용하여 불필요한 작업을 방지할 수 있습니다.

  • pendingProps: 파이버가 실행을 시작할 때 설정되며, 해당 파이버가 처리 중인 작업에 대한 속성을 나타냅니다.
  • memoizedProps: 파이버의 실행이 끝날 때 설정되며, 해당 파이버의 이전 작업에서 사용된 속성을 나타냅니다.

pendingWorkPriority

파이버가 나타내는 작업의 우선순위를 나타내는 숫자입니다. 이를 통해 리액트는 어떤 작업을 먼저 처리할지 결정할 수 있습니다. 우선순위가 높은 작업부터 처리되며, 작업의 종류에 따라 다양한 우선순위 수준이 있습니다.

alternate

대체 파이버는 동일한 컴포넌트를 나타내지만, 이전과 비교했을 때 업데이트된 상태를 가지고 있습니다. 이를 통해 리액트는 이전 상태와 현재 상태를 비교하여 필요한 업데이트를 최소화할 수 있습니다.

output

출력은 리액트 애플리케이션의 실제 렌더링 결과물을 나타냅니다.
이는 화면에 표시되는 HTML 요소와 같은 것들을 의미하며, 이를 통해 사용자가 애플리케이션의 상태를 시각적으로 확인할 수 있습니다.


Fiber는 렌더링 작업을 비동기적으로 처리할 수 있도록 지원합니다.

다음은 Fiber를 사용하여 네트워크를 요청한 후 데이터를 받아와서 UI를 업데이트하는 예시입니다.

import React, { useState, useEffect } from 'react';

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      // 데이터를 가져오는 데 시간이 오래 걸릴 수 있는 비동기 작업
      const result = await fetch('https://api.example.com/data');
      const jsonData = await result.json();
      
      // 데이터를 받아온 후 상태 업데이트
      setData(jsonData);
    };

    fetchData();
  }, []); // 최초 렌더링 시에만 실행되도록 빈 배열 전달

  return (
    <div>
      {data ? (
        <div>{/* 데이터를 이용한 UI 업데이트 */}</div>
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
}

export default App;

결론적으로 Fiber는 리액트의 렌더링 성능을 향상시키고, 특히 대규모 애플리케이션에서 더 나은 사용자 경험을 제공합니다.
비동기화된 렌더링을 통해 긴 렌더링 작업을 비동기적으로 처리하여 성능을 향상시키고, 우선순위 기반 작업 처리를 통해 사용자 상호작용의 응답성을 높입니다.
또한, 작업 단위를 분할함으로써 부드러운 UI 업데이트를 가능하게 합니다.


참고
https://velog.io/@arthur/%EB%B2%88%EC%97%AD-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%8F%99%EC%9E%91%EC%9D%98-%EA%B1%B0%EC%9D%98-%EC%99%84%EB%B2%BD%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-A-Mostly-Complete-Guide-to-React-Rendering-Behavior#%EB%AA%A9%EC%B0%A8
https://velog.io/@hamjw0122/FE-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%EA%B0%80%EC%83%81-DOM-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0#-fiber
https://m.blog.naver.com/dlaxodud2388/223195103660
https://velog.io/@jangws/React-Fiber
https://youtube.com/watch?v=0ympFIwQFJw&list=PLxRVWC-K96b0ktvhd16l3xA6gncuGP7gJ&index=2

profile
no namae wa

0개의 댓글