useReducer vs useState) 2-1. React fiber

Sal Jeong·2022년 9월 24일
0
post-thumbnail

저번에 UseReducer를 사용한 useToggle hook을 발견하면서, 왜 useReducer가 기존의 useState와 useCallback을 사용한 useToggle보다 메모리 면에서 더 나은 점이 있는지, 알아보기로 했다.

Reactjs의 source에서 hooks는 실행될 때 linked list에 담겨서 차례대로 실행되는데,

// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.

그곳에서 주석처리 된 부분이 hooks는 리액트 fiber의 memoized fields에 저장되는 linkedlist로 처리된다고 적혀있었다.

1. 그렇다면 fiber와 기존 render algorithm의 퍼포먼스 차이는?

fiber structure 로 구현된 시어핀스키 삼각형

https://claudiopro.github.io/react-fiber-vs-stack-demo/
에서 발취

기존 stack structure로 구현된 시어핀스키 삼각형

꽤나 퍼포먼스의 차이가 있는 것을 볼 수 있는데, 도대체 왜일까?

2. 개념 이해

공식 독스에 의하면 fiber는 리액트의 새 reconciler이다.

1. reconciliation이란?

reconciliation - 기존 react에서 render()가 수행될 때, 빠른 rendering을 위해서 전체 DOM tree를 도는 것이 아니라, ReactJS에서 만든 객체(VDOM)끼리의 비교를 통해 바뀐 부분만을 DOM Tree에 적용하는 개념으로 알고 있음.

1. The algorithm will not try to match subtrees of different component types. If you see yourself alternating between two component types with very similar output, you may want to make it the same type. In practice, we haven’t found this to be an issue.
Keys should be stable, predictable, and unique. Unstable 2. keys (like those produced by Math.random()) will cause many component instances and DOM nodes to be unnecessarily recreated, which can cause performance degradation and lost state in child components.

휴리스틱 연산에 기반한 이를 돕기 위해서 개발자는
1. 유사한 결과(subtree)를 만들어내는 컴포넌트의 경우, 리액트에서는 이를 완전히 새로운 트리로 만들기 때문에 하나로 합치는 것이 퍼포먼스에 좋다.
2. key를 지정해줄 경우 무조건 unique값을 넣어줘야만 한다.
라고만 알고 있음.

2. Scheduling이란?

This is a subtle distinction but a powerful one. Since you don’t call that component function but let React call it, it means React has the power to delay calling it if necessary. In its current implementation React walks the tree recursively and calls render functions of the whole updated tree during a single tick. However in the future it might start delaying some updates to avoid dropping frames.

기존의 react stack reconciler에서는 update를 재귀적으로, 한 플로우에 진행하기 때문에, animation과 같은 high-priority 작업의 frame drop과 같은 문제가 일어날 수 있다.(위 도형참고) fiber에서는 이를 방지하기 위해 특정한 업데이트를 임의로 delay할 수 있도록 한다.

이러한 Scheduling은 time-based나 priority-based일 수 있다.
핵심은, high-priority work가 low-priority work보다 먼저 실행된다는 것이다.

3. requestIdleCallback이란 ?

// mdn requestIdleCallback 설명

window.requestIdleCallback() 메서드는 브라우저의 idle 상태에 호출될 함수를 대기열에 넣습니다. 이를 통해 개발자는 애니메이션 및 입력 응답과 같은 대기 시간이 중요한 이벤트에 영향을 미치지 않고 메인 이벤트 루프에서 백그라운드 및 우선 순위가 낮은 작업을 수행 할 수 있습니다. 함수는 일반적으로 first-in-first-out(FIFO) 순서로 호출됩니다. 하지만, timeout 옵션이 지정된 callback은 제한 시간이 지나기 전에 이들을 실행하기 위해 순서에 맞지 않게 호출될 수 있습니다.

requestAnimationFrame 은 다음 animation frame 실행 전, 코드를 실행하게 해주는 web api이다(high-priority work)

requestIdleCallback이란 이와 비슷하게, frame의 끝 남는 시간에 callback을 실행시키는 web api이다.(low priority work)

// https://gist.github.com/velotiotech/fd8da18270f04643bc448cdd2486ced2#file-request-idle-callback-usage-js

requestIdleCallback(lowPriorityWork);

function lowPriorityWork(deadline) {
    while (deadline.timeRemaining() > 0 && workList.length > 0)
      performUnitOfWork();
  
    if (workList.length > 0)
      requestIdleCallback(lowPriorityWork);
  }

When this callback function is called, it gets the argument deadline object. As you can see in the snippet above, the timeRemaining function returns the latest idle time remaining. If this time is greater than zero, we can do the work needed. And if the work is not completed, we can schedule it again at the last line for the next frame.

위 api는 deadline object를 arg로 받아 timeRemaining()으로 다음 프레임까지 남은 시간을 파악한 뒤, 0 이상일때 콜백을 실행한다.
만약 worklist가 남아있다면, requestIdleCallback을 재귀호출하여 다음 프레임 실행 이후 실행할 수 있도록 한다.

3. fiber - React's new reconciliation algorithm

리액트 15까지 사용된 것이 가상 돔(virtual DOM, Stack reconciler)이며, 스택이라는 이름처럼 스택 자료구조를 사용하며, virtual DOM의 diffing 작업을 재귀로, 동기적으로 수행한다. 따라서 위의 결과처럼 60fps의 애니메이션을 표시하는데 문제가 생길 수 있게 된다.

  1. Fiber의 메인 골은 incremental rendering이다.

  2. 이를 통해서 더 스무스한 UI 애니메이션과 제스쳐 핸들링을 통해 반응성을 향상시키는 것이 목적이다.

  3. Fiber reconciler는 diffing작업을 재귀적으로 수행하는 것이 아닌,
    작업을 chunks 단위로 나누고 이를 여러 프레임을 통해 수행한다.

  4. 해당 작업들은 우선순위에 따라 수행되고(work), 멈추고(pause), 재사용되고(reuse), 취소(abort)될 수 있다.

또한, Fiber를 통해

1. multiple elements를 리턴하거나,

<>
  <Ele />
  <Ele2 />
</>

2. portal과 같은 기능을 사용할 수 있게 되었고,

render() {
  // React는 새로운 div를 생성하지 *않고* `domNode` 안에 자식을 렌더링합니다.
  // `domNode`는 DOM 노드라면 어떠한 것이든 유효하고, 그것은 DOM 내부의 어디에 있든지 상관없습니다.
  return ReactDOM.createPortal(
    this.props.children,
    domNode
  );
}
// 기존 root이 아닌, DOM의 다른 부분에 element를 렌더링한다

3. ErrorBoundary를 사용할 수 있게 되었다.

Funtional Component가 메인이 된 후,
ComponentDidCatch를 사용한 부분이 항상 아쉬었는데, 이걸 제대로 쓰면 디버깅이 아주 편해질 것 같다!

이러한 작업을 수행하기 위해서 리액트는 우선순위(priority)를 적용해, 메인 쓰레드에서 작업을 계속해서 교체하면서 연산하게 된다.

4. fiber와 Fiber

fiber 역시 vDOM 처럼 자바스크립트의 객체이다. 리액트 엘리먼트와 DOM tree node를 뜻한다.
Fiber는 React의 새로운 reconciler를 뜻한다.

function App() {
    return (
      <div className="wrapper">
        <div className="list">
          <div className="list_item">List item A</div>
          <div className="list_item">List item B</div>
        </div>
        <div className="section">
          <button>Add</button>
          <span>No. of items: 2</span>
        </div>
      </div>
    );
  }
 
  ReactDOM.render(<App />, document.getElementById('root'));
  

https://www.velotio.com/engineering-blog/react-fiber-algorithm 에서 발췌

위 간단한 예시에서 리액트는 위 코드를 파싱하며 fiber의 tree 구조를 형성한다. 위 엘리먼트의 개수만큼 fiber가 형성되며, 임의로 key를 부여한다.(wrapper = W, list = L, list_item = LA, LB 와 같이)

여기서 Fiber는 실제로 tree 구조가 아니라 linked list로 node를 저장한다.
그리고 key값으로 부모, 자식, 형제 노드의 관계를 정의한다. 예를 들어서, list = L 이 부모, list_item = LA, LB가 자식이 된다.

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js#L49

react-codebase에서 위 fiber 구조의 코드를 직접 볼 수 있다.



// A Fiber is work on a Component that needs to be done or was done. There can
// be more than one per component.
// Fiber는 Component에서 해야 할 연산, 혹은 완료된 연산을 가르킨다.
// 하나의 컴포는트에 Fiber는 하나 이상일 수 있다.

export type Fiber = {
  // These first fields are conceptually members of an Instance. This used to
  // be split into a separate type and intersected with the other Fiber fields,
  // but until Flow fixes its intersection bugs, we've merged them into a
  // single type.

  // 컨셉상으로, Fiber는 intstance 의 멤버이다.
  // Fiber는 component 당 여러개가 존재할 수 있기 때문에, 처음에는 다른 타입과 다른 Fiber와 intersect하지만,
  // react 에서 intersection bugs를 수정하게 되면, 이를 single type으로 합치게 된다.

  // An Instance is shared between all versions of a component. We can easily
  // break this out into a separate object to avoid copying so much to the
  // alternate versions of the tree. We put this on a single object for now to
  // minimize the number of objects created during the initial render.

  // instance는 모든 버전의 component 사이에(react의 트리 버전) 공유가 된다,
  // react에서는 이 인스턴스를 쪼개서 다른 객체들로 만들어 줌으로써 다른 버전의 tree가 생성될 때 copy 되는것을 최소화 한다.
  // 이 원칙을 single object마다 적용해서, render 시 객체 생성 횟수를 줄인다.

  // Tag identifying the type of fiber.
  // 해당 fiber의 type
  tag: WorkTag,

  // Unique identifier of this child.
  // 이 노드의 유니크 키값
  key: null | string,

  // The value of element.type which is used to preserve the identity during
  // reconciliation of this child.
  // reconciliation 단계 중 해당 node의 엘리먼트값을 기억하기 위한 값.
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  // 해당 fiber가 functional 인지 class인지를 기억하는 값. 
  type: any,

  // The local state associated with this fiber.
  // 해당 fiber가 가지는 state의 값.
  stateNode: any,

// Conceptual aliases
// parent : Instance -> return The parent happens to be the same as the
// return fiber since we've merged the fiber and instance.
// 컨셉상의 용어:
// instance: class component에서 state나 내부값을 저장하는 this를 지칭. functional에서는 없다.
// parent: instance -> fiber와 instance를 merge한 개념.


  // Remaining fields belong to Fiber

  // The Fiber to return to after finishing processing this one.
  // This is effectively the parent, but there can be multiple parents (two)
  // so this is only the parent of the thing we're currently processing.
  // It is conceptually the same as the return address of a stack frame.

  // 해당 fiber의 작업을 마치면 Fiber로 return된다.
  // 기존의 parent에서 children의 작업을 맡았던 것보다 효율적이다. 하지만, parent가 하나 이상이 될 수 있다.
  // 그래서, 이 프레임은 현재 연산하는 것의 유일한 부모가 되고? 이것은 stack frame의 주소와 같다고 볼 수 있다.
  return: Fiber | null,

  // Singly Linked List Tree Structure.
  // singly linked list 트리 구조????
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,

  // The ref last used to attach this node.
  // I'll avoid adding an owner field for prod and model that as functions.
  // 이 node에 사용되었던 이전 ref,
  // 
  ref:
    | null
    | (((handle: mixed) => void) & {_stringRef: ?string, ...})
    | RefObject,

  // Input is the data coming into process this fiber. Arguments. Props.
  // 이 fiber에 전달되는 args, props
  pendingProps: any, // This type will be more specific once we overload the tag.
  memoizedProps: any, // The props used to create the output.

  // A queue of state updates and callbacks.
  // update 상태, callback 상태
  updateQueue: mixed,

  // The state used to create the output
  // 이전 state.
  memoizedState: any,

  // Dependencies (contexts, events) for this fiber, if it has any
  // event나 context와 같은 dependencies.
  dependencies: Dependencies | null,

  // Bitfield that describes properties about the fiber and its subtree. E.g.
  // the ConcurrentMode flag indicates whether the subtree should be async-by-
  // default. When a fiber is created, it inherits the mode of its
  // parent. Additional flags can be set at creation time, but after that the
  // value should remain unchanged throughout the fiber's lifetime, particularly
  // before its child fibers are created.
  mode: TypeOfMode,

  // Effect
  flags: Flags,
  subtreeFlags: Flags,
  deletions: Array<Fiber> | null,

  // Singly linked list fast path to the next fiber with side-effects.
  // 이전에 hooks는 linked list로 저장되므로, useEffect도 마찬가지이다.
  nextEffect: Fiber | null,

  // The first and last fiber with side-effect within this subtree. This allows
  // us to reuse a slice of the linked list when we reuse the work done within
  // this fiber.
  // linked list의 head, tail -> 링크드리스트의 재사용을 위해 지정.
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  lanes: Lanes,
  childLanes: Lanes,

  // This is a pooled version of a Fiber. Every fiber that gets updated will
  // eventually have a pair. There are cases when we can clean up pairs to save
  // memory if we need to.
  // pool 된 Fiber. 모든 fiber는 하나가 아닌 복제본을 하나 가지는데, 메모리 용량에 중점을 둘 경우 이 복제본을 제거할 수 있다.
  alternate: Fiber | null,

  // Time spent rendering this Fiber and its descendants for the current update.
  // This tells us how well the tree makes use of sCU for memoization.
  // It is reset to 0 each time we render and only updated when we don't bailout.
  // This field is only set when the enableProfilerTimer flag is enabled.
  // 이 Fiber와 decendants를  render할시 걸린 진짜 시간.
  // shouldComponentUpdate가 얼마나 효율적인지 확인하기 위함.
  // enableProfilerTimer를 true로 했을 때만 계산됨.
  actualDuration?: number,

  // If the Fiber is currently active in the "render" phase,
  // This marks the time at which the work began.
  // This field is only set when the enableProfilerTimer flag is enabled.
  // render 페이즈에서 이 Fiber가 작업을 시작한 진짜 시간
  // enableProfilerTimer를 true로 했을 때만 계산됨.
  actualStartTime?: number,

  // Duration of the most recent render time for this Fiber.
  // This value is not updated when we bailout for memoization purposes.
  // This field is only set when the enableProfilerTimer flag is enabled.
  // 이 Fiber의 가장 최근 render time 기록
  // EnableProfilerTimer를 true로 했을 때만 계산됨.
  selfBaseDuration?: number,

  // Sum of base times for all descendants of this Fiber.
  // This value bubbles up during the "complete" phase.
  // This field is only set when the enableProfilerTimer flag is enabled.
  // 해당 Fiber 의 자식까지 모든 render를 끝마치는 시간
  // event bubbling처럼 complete phase에 적용됨
    // This field is only set when the enableProfilerTimer flag is enabled.

  treeBaseDuration?: number,

  // Conceptual aliases
  // workInProgress : Fiber ->  alternate The alternate used for reuse happens
  // to be the same as work in progress.
  // workInProgress: Fiber -> alternate 재사용이 일어날 시 복제본을 사용하는 것
  // __DEV__ only

  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
  _debugNeedsRemount?: boolean,

  // Used to verify that the order of hooks does not change between renders.
  // hook의 순서를 바꾸지 않기 위한 순서
  _debugHookTypes?: Array<HookType> | null,
};

위 내용에 대해서는 이해력이 딸리는 고로 완벽하게 이해할 수는 없지만,
적어도

1. fiber는 render phase의 최소 단위

2. Fiber는 fiber가 합쳐진, 컴포넌트를 render하기 위한 fiber의 조합.

3. Fiber의 sibling과 child는 Singly LinkedList Tree 구조로 짜여져 있음.?????

4. hooks 액션들은 linked list로 저장되어서 pair로 pooling되어 저장된다.(이전 훅 리스트, 새로운 훅 리스트) Fiber 그 자체도 마찬가지로 pooling 된다.

5. React Fiber는 어떻게 동작하나?

Fiber는 위에서 말했듯이 Linked List tree를 생성해서 업데이트 한다.

current tree와 새롭게 생성된 workInProgress 트리를 비교하는 방법으로 렌더링하게 되는데,

1. 현재 그려진 트리를 current, current UI라고 부른다.

2. update 시, react에서는 새롭게 업데이트된 엘리먼트를 기반으로 workInProgress 트리를 생성한다.

3. 위 두 트리를 비교하고, workInProgress 트리를 rendering 한다.

4. workInProgress가 render되면, 해당 트리가 current tree가 된다.

이것을 그림으로 나타내면 이렇게 된다.


// current tree, workInProgress tree

1. start: Fiber가 traversal을 시작하면서 React element의 맨 상위부터 fiber node를 생성한다.

2. child: 상위부터 child로 내려가면서 element 당 fiber node를 생성한다. dfs식으로 모든 child까지 생성된다

3. sibling: 그 후 형제 노드가 존재하는지 채크한다. 존재한다면, 2. 으로 해당 노드의 서브트리를 모두 fiber node화 한다. 형제가 없다면 부모 노드로 돌아간다.

모든 fiber는 자식, 형제, 부모 값을 가진다.

child: Fiber | null,
  sibling: Fiber | null,
  index: number,

이것이 링크드리스트의 포인터가 된다.


function App() {    // App
    return (
      <div className="wrapper">    // W
        <div className="list">    // L
          <div className="list_item">List item A</div>    // LA
          <div className="list_item">List item B</div>    // LB
        </div>
        <div className="section">   // S
          <button>Add</button>   // SB
          <span>No. of items: 2</span>   // SS
        </div>
      </div>
    );
  }
 
  ReactDOM.render(<App />, document.getElementById('root'));  // HostRoot

위의 코드를 flowchart로 표현하면 이렇게 된다.

initial render 과정

1. App 컴포넌트는 root div 아래에 형성 되고, 'root' id를 가진다.

2. traverse가 시작되면서 Fiber에서는 root fiber를 형성한다. 모든 Fiber tree는 하나의 상위 root node를 가지고, App의 상위는 HostRoot가 된다. 만약 하나 이상의 React App을 import할때 App의 개수는 하나 이상이 될 수 있다.

3. 처음으로 렌더링 될 때에는 만들어진 트리(링크드 리스트)가 없기 때문에, Fiber가 위 순서대로 모든 노드를 돌며 fiber node를 생성한다. 이것이 위 그림의 current tree가 된다.

https://github.com/facebook/react/blob/769b1f270e1251d9dbdce0fcbd9e92e502d059b8/packages/react-reconciler/src/ReactFiber.js#L414

위 링크의 createFiberFromTypeAndProps function이 react element를 fiber로 바꾸어주는 역할을 한다.
class component라면 instance를 생성하고 데이터를 넘겨주며, host component(div or span같은)라면 자식들에게 data/props를 넘겨주는 역할을 한다.

다시 정리하면, 위 코드를 렌더링하는데 우선

  1. App 컴포넌트 fiber 생성
  2. 그 다음 상위 W fiber 생성.
  3. W의 자식 L fiber.
  4. L의 자식 LA, LB fiber.
  5. 자식이 없으므로 다시 parents 리턴
  6. 형제 노드 S fiber 생성,
  7. S 의 자식 SA, SB fiber 생성

가 된다.

update 과정

initial render와 다르게 이미 tree 자체가 존재하는 상태이기 때문에 current tree가 아닌 WorkInProgress Tree가 생성된다.
모든 노드를 도는 것은 같지만, 새로운 fiber를 생성하는 것이 아니라, 이미 존재하는 fiber에서 새로운 data/props를 머지하여 업데이트한다.

이전 리액트 15에서는 stack reconciler는 이 과정을 동기적으로 처리하므로, WorkInProgress 트리가 연속적으로 생겨나는 과정(animation과 같은 이유로), 에서는 update가 연속해서 일어나지만, 첫 번째 업데이트가 끝날 때까지 두번째 업데이트를 진행할 수가 없었다.

React Fiber는 이 문제는 units of works로 나누어서 해결했다.
재사용이나 정지, 중지를 할 수 있도록 각 unit에 우선순위 개념을 두었다.
이러한 Fiber는 여러 units of works로 또 나뉘어지는데 이것이 fiber가 된다. fiber는 이 work를 requestIdleCallback에 기반한 deadline 개념을 사용한다. 모든 업데이트는 animation(가장 높은 우선도), 유저의 data input 혹은 data fetching과 같이 각각의 우선순위를 가지고, Fiber는 우선순위가 높은 작업에 대해 requestAnimationFrame을,
낮은 작업에서 requestIdleCallback을 사용한다.

Fiber는 이렇게 작업을 여러 units로 나누어서 활용하고, 한 frame이 실행되고 남은 시간(deadline)에 pending work들을 실행하게 된다. 남은 작업들은 다음 frame의 남은 시간에 또 실행된다. 이것이 Fiber가 동기적이 아니라 작업을 중지 혹은 재사용할 수 있는 이유가 된다.

Render Phase

https://github.com/facebook/react/blob/f765f022534958bcf49120bf23bc1aa665e8f651/packages/react-reconciler/src/ReactFiberScheduler.js#L1136

실제 tree traversal과 deadline의 활용이 이 단계에서 일어난다.
Fiber의 내부 로직이 실행되며, 유저들에게는 보이지 않는 단계이다.

이 단계를 또한 reconciliation phase라고 부르기도 한다.
Fiber는 current tree와 함께 WorkInProgress Tree를 돌며 비교하면서, 이때는 workLoop 펑션을 반복해서 실행하게 된다.
더 자세하게, 이 단계는 begin과 complete단계로 나눌 수 있다.

Begin Step

https://github.com/facebook/react/blob/f765f022534958bcf49120bf23bc1aa665e8f651/packages/react-reconciler/src/ReactFiberScheduler.js#L1136

https://github.com/facebook/react/blob/f765f022534958bcf49120bf23bc1aa665e8f651/packages/react-reconciler/src/ReactFiberBeginWork.js#L1076

위 workLoop를 확인해 본다면, workLoop 펑션은 performUnitOfWork -> beginWork 순으로 작동한다. 실제 beginWork에서 fiber가 처리된다.

beginWork에서 fiber가 pending status의 작업이 없다면, 실제로 작업하지 않고 그냥 넘겨버린다.
fiber의 재사용이 일어나는 것이며, 이렇게 beginWork가 완료되면 child fiber를 리턴하여 계속해서 작업을 하게 되며, child가 없다면 null을 리턴하며, 완료 시 completeUnitOfWork를 리턴한다. 여기서부터 Complete step 이 시작된다.

Complete Step

completeUnitOfWork -> completeWork를 부르면서 작업중인 하나의 unit of work가 완료된다. completeUnitOrWork는 형제 fiber를 부르거나, 없다면 부모를 다시 호출하면서 unit of work의 끝을 알려준다. 이런식으로 null(모든 leaf node가 다 호출되었을 때)까지 반복되어 마지막 부모 즉 'root' 노드가 불릴 때까지 계속된다. beginWork와 마찬가지로, completeWork 자체에서 작업이 일어나며, completeUnitOfWork는 그저 iteration의 시작을 알리는 역할을 한다.
이러한 phase의 결과로 effect list가 실행된다. side-effects라고 보통 불리며, node의 insert, update 혹은 delete 가 실행되거나 lifecycle 메소드가 불리게 된다. 이러한 fiber들은 effect tag가 붙여서 저장된다.

여기가 주목해야할 부분이다! useState 역시 side-effects의 일부분 아닌가??? useReducer와 useState를 비교할때 이 부분 코드를 직접 보아야 한다고 생각함.

여기까지 되었다면 commit phase가 실행된다.

Commit Phase

https://github.com/facebook/react/blob/95a313ec0b957f71798a69d8e83408f40e76765b/packages/react-reconciler/src/ReactFiberScheduler.js#L2306

commit phase는 이전 버전의(15버전) React처럼 실제 render가 일어나는 단계이다. 이 부분의 결과의 변화가 유저에게 보여지게 되고, 이 부분은 여전히 synchronouse하게 일어난다.

맨 먼저 Fiber는 current tree 혹은 finishedWork로 인해 만들어진 트리(workInProgress tree)를 이 단계에서 가지고 있다.
이외에 effect list를 가지고 있다.
effect list는 fiber로 이루어진 linked list인데, 이 작업의 결과로 side-effects가 일어난다. 이 render phase 단계에서 side-effects가 실행되고, nextEffect 포인터를 통해 다음 linked list node(effect fiber)로 넘어가면서 실행된다.
이 단계에서 실제로 돌아가는 펑션은 completeRoot으로, 새로운 workInProgress 트리가 current tree로 변경 -> ui에 반영된다.

뭐 더 할말이 있으면 좋겠지만, 설명한 내용만 봤는데도 이정도가 된다.
아마 실제로 제대로 코드를 뜯어보지 않는다면 더 크게 할 말이 없지 않을까??

지금으로써는 requestIdleCallback,과 requestAnimationFrame을 사용해서, frame 이후 남는 시간동안 linked list로 된 작업을 수행하고, 작업 중간중간 다음 작업을 넘기거나 해서 속도를 더 빠르게 한다는 거 외에는 이해가 잘 안가기 때문에...

그렇지만, 일단 이론적인 내용은 알았으니 다시 workInProgressHooks 부분을 다시 확인해서 useState와 useReducer의 퍼포먼스를 비교해 볼 수는 있겠다.

참고한 URL:

https://reactjs.org/docs/reconciliation.html

https://reactjs.org/docs/design-principles.html#scheduling

https://claudiopro.github.io/react-fiber-vs-stack-demo/

https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js#L49

https://www.velotio.com/engineering-blog/react-fiber-algorithm

https://dzone.com/articles/understanding-of-react-fiber-architecture

https://developer.chrome.com/blog/using-requestidlecallback/

https://github.com/tranbathanhtung/react-fiber-implement

https://github.com/koba04/react-fiber-resources

09/25: Fiber initial render phase까지 작성

09/27: Fiber update render까지 작성

profile
Can an old dog learn new tricks?

0개의 댓글

관련 채용 정보