리액트 화이바....?
어디선가 계속 언급되던 React Fiber를 살펴봐야겠다고 마음을 먹었다.
react-fiber-architecture 문서 번역
Fiber는 React v16에서 리액트의 핵심 알고리즘을 재구성한 새 재조정(Reconciliation) 엔진이다. React Fiber의 목표는 애니메이션, 레이아웃, 제스처, 중단 또는 재사용 기능과 같은 영역에 대한 적합성을 높이고 다양한 유형의 업데이트에 우선 순위를 지정하는 것이다.
핵심 기능은 렌더링을 증분하는 것이다. 이는 렌더링 작업을 여러 덩어리로 나누어 여러 프레임에 분산하는 기능이다.
문서에 의하면, 이 기능의 주요 목표는 다음과 같다.
i. 중단 가능한 작업을 덩어리로 나누기
ii. 진행 중인 작업의 우선순위를 지정하고, 리베이스하고 재사용
iii. 리액트의 레이아웃을 지원하기 위해 부모와 자식 간에 yield back and forth
iv. render()로부터 다수 엘리먼트들을 반환
v. 에러 바운더리에 대한 더 나은 지원
Virtual DOM은 Real DOM의 in-memory 표현이다. UI 표현은 메모리에 저장되며 Real DOM과 동기화된다. 이는 호출되는 렌더 함수에서 화면에 표시되는 요소가 되는 사이에 발생한 단계이다. 이러한 전체 과정을 재조정(reconciliation)
이라 한다.
Virtual DOM은 특정 기술이라기보다는 패턴에 가깝다. React에서는 Virtual DOM이란 UI를 나타내는 객체로 통용되며, React elements
와 연관된다.
React는 컴포넌트 트리에 대한 추가 정보를 포함하기 위해 fibers
라는 내부 객체를 사용한다.
Virtual DOM은 브라우저 API 위에 있는 JavaScript 라이브러리에서 구현되는 개념이다.
React가 변경해야 할 부분을 결정하기 위해 한 트리를 다른 트리와 비교하는 데 사용하는 알고리즘이다.
React API의 핵심 아이디어는 업데이트를 통해 전체 앱을 다시 렌더링하도록 생각하는 것이다. 이를 통해 개발자는 앱을 특정 상태에서 다른 상태(A에서 B로, B에서 C로, C에서 A로 등)로 효율적으로 전환하는 방법에 대해서는 걱정하지 않고, 선언적
으로 개발할 수 있다.
하지만 각 변경사항마다 전체 앱을 다시 렌더링하면 성능이 좋지 않다.
This demo shows the differences between Stack and Fiber by rendering a Sierpinski triangle that constantly shrinks and grows, and whose nodes have a value that increments by one every second.
Fiber Example
Stack Example
리액트는 이를 해결하기 위해 뛰어난 성능을 유지하면서 전체 앱을 리렌더링할 수 있는 최적화 방법을 고안했다. 이러한 최적화 방법들은 재조정(Reconciliation)
이라고 불리는 과정의 일부이다.
재조정은 Virtual DOM으로 널리 이해되고 있는 것의 뒤에 있는 알고리즘이다.
리액트 애플리케이션을 렌더링하면 앱을 묘사하는 노드의 트리가 생성되어 메모리에 저장된다. 그다음 이 트리는 렌더링 환경으로 전달된다(브라우저 환경이라면 이 트리는 일련의 DOM 조작으로 변환된다). 앱이 setState
등을 통해 업데이트되면, 새 트리가 생성된다. 새 트리는 렌더링된 앱을 업데이트하는 데 필요한 작업을 계산하기 위해 이전의 트리와 구분된다(diff).
알고리즘의 요점은 다음과 같다.
key
를 통해 구분(diff)된다. 따라서 key
는 "안정적이고, 예측 가능하고, 유니크해야 한다."DOM은 리액트가 렌더할 수 있는 렌더링 환경 중 하나일 뿐이다. 이외에 다른 주요한 렌더링 환경으로는 React Native
를 통한 native iOS와 Android views 등이 있다. 이러한 이유로 Virtual DOM
은 약간 잘못된 명칭이라고 할 수 있다.
리액트는 재조정(Reconciliation)
과 렌더링
을 별개의 단계가 되도록 설계됐다. Reconciler
은 트리의 어떤 부분이 변경됐는지 계산한다. 이후Renderer
은 계산된 정보를 앱을 실제로 업데이트하는 데 사용한다.
이러한 분리는 React DOM
과 React Native
가 React Core가 제공하는 동일한 Reconciler
를 공유하면서 각자 자체적인 Renderer
의 사용을 가능하게 한다.
Fiber
는 Reconciler
를 재구현한다.
리액트는 현재 스케줄링의 이점을 크게 활용하지 않으며, 업데이트 결과 전체 하위 트리가 즉시 리렌더링된다. 스케줄링의 이점을 활용하기 위해 리액트의 핵심 알고리즘을 정비하는 것이 Fiber
의 원동력이다.
위에서 살펴봤듯이, Fiber의 목표는 리액트가 스케줄링의 이점을 활용할 수 있도록 하는 것이다. 구체적으로는 다음과 같은 일을 할 수 있어야 한다.
이들을 하기 위해선, 먼저 단위별로 세분화해야 하며, 이 과정 자체가 Fiber라고 할 수 있다. Fiber는 이처럼 작업의 한 단위(unit of work)를 나타낸다.
데이터의 함수로서 리액트 컴포넌트
라는 개념은, 보통 다음과 같이 표현된다.
v = f(d)
// view는 data에 대한 function의 결과
리액트 앱을 렌더링하는 것은 다른 함수들을 호출하는 body를 가진 함수를 호출하는 것과 동일하다고 볼 수 있다. 이러한 비유는 fiber에 대해 생각할 때 유용하다.
컴퓨터가 프로그램 실행을 추적할 때 일반적으로 Call Stack
을 사용한다. 함수가 실행되면 스택에 새 stack frame이 추가된다(stack frame은 해당 함수에 의해 수행되는 작업을 의미한다.).
UI 렌더링에 최적화되도록 Call Stack의 동작을 사용자 지정할 수 있다면 좋지 않을까? Call Stack과 stack 프레임을 조작할 수 있다면 좋지 않을까?
그것이 Fiber의 목적이다. Fiber는 리액트 컴포넌트에 특화된 stack의 재구성
이다. 하나의 Fiber를 virtaul stack frame
으로 간주할 수 있다.
stack을 재구성함으로써 얻는 이점은 stack frame을 메모리에 보관하고 원하는 경우 언제든지 실행할 수 있다는 것이다. 이는 스케줄링 목표를 달성하는 데 매우 중요하다.
stack frame을 수동으로 처리하면 스케줄링뿐만 아니라 concurrency 및 error boundary와 같은 기능들에 대한 잠재력을 확보할 수 있다.
Fiber는 컴포넌트
및 컴포넌트의 입력과 출력
에 대한 정보를 포함한 자바스크립트 객체
이다.
Fiber는 stack frame이기도 하지만, 컴포넌트의 인스턴스이기도 하다.
Fiber에 속하는 중요한 field들은 다음과 같다.
Fiber의 type
과 key
는 리액트 요소에 그것들과 비슷하다.
type 및 key는 재조정 시에 fiber가 재사용될 수 있는지 판단할 때 사용된다.
이 필드들은 다른 fiber들을 가리키는데, fiber의 재귀적 트리 구조를 묘사한다고 할 수 있다.
child
fiber는 컴포넌트의 렌더 메서드에 의해 반환된 값들이다.
function Parent() { return <Child /> }
위 예시에서, Parent
의 child fiber는 Child
이다.
sibling
필드는 render
가 여러 자식들을 반환하는 케이스를 설명한다. child fiber들은 첫째 child를 head로 두는 단일 연결 리스트(singly linked list)를 형성한다.
function Parent() { return [<Child1 />, <Child2 />] }
위 예시에서, Child1과 Child2는 sibling
이다.
return
fiber는 프로그램이 현재의 fiber를 처리한 후 반환해야 하는 fiber이다. 개념적으로는 stack frame의 return address이다. 또한, parent fiber라고 생각할 수 있다.
fiber에 여러 child fiber들이 있으면, 각 child fiber의 return
fiber는 parent이다. 따라서 위에서 살펴본 예시에서, Child1
과 Child2
의 return
fiber는 Parent
이다.
개념적으로, props는 함수의 arguments이다. fiber의 pendingProps
는 그것의 실행 시작 시 설정되고, memoizedProps
는 그것의 실행 끝에 설정된다.
새로 들어오는 pendingProps는 memoizedProps와 같을 때, fiber의 이전 결과가 재사용될 수 있으며, 불필요한 작업을 방지할 수 있다.
fiber가 나타내는 작업들의 우선순위.
개념적으로, fiber의 output은 함수의 반환 값이다.
모든 fiber는 결국 output을 가지지만, output은 호스트 컴포넌트에 의해 리프 노드에서만 생성된다. output은 트리 위로 전달된다.
렌더링 환경에 대한 변경사항을 flush할 수 있도록, output은 최종적으로 렌더러에게 전달된다. output이 생성되고 업데이트되는 방법을 정의하는 것은 렌더러가 할 일이다.
{
type: any, // For a class component, it points to constructors; for a DOM element, it specifies HTML tags
key: null | string, // The unique identifier
stateNode: any, // Save the references to class instances of components, DOM nodes, or other React element types associated with the fiber node
child: Fiber | null, // The first child node
sibling: Fiber | null, // The next child node
return: Fiber | null, // The parent node
tag: WorkTag, // Define the type of fiber action. For more information,see https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactWorkTags.js
nextEffect: Fiber | null, // The pointer to next node
updateQueue: mixed, // The queue for status update, callback function, and DOM update
memoizedState: any, // The fiber state for output creation
pendingProps: any, // The props that are updated from the new data of React elements and need to be applied to child components or DOM elements
memoizedProps: any, // The props used to create the output during the previous rendering
// ……
}
참고
https://github.com/acdlite/react-fiber-architecture
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js
https://www.alibabacloud.com/blog/a-closer-look-at-react-fiber_598138
https://blog.kiprosh.com/react-fiber/
글 잘 읽었습니다. 좋은 글 번역 감사합니다 😆