Skip to main content

Introduction to React Fiber

❓ What is React Fiber?

React Fiber is a completely backward-compatible reimplementation of React's core algorithm that is shipped with React >= 16.8. The goal is to increase its suitability for areas like animation, layout, and gestures.

-- Andrew Clark, React Fiber Architecture

React Fiber is a reimplementation of the React's reconcilization algorithm introduced since React v16.8, which aims to solve known performance issues in the previous implementaion due to its synchronous nature.

With asynchronous design in mind, React Fiber introduced a Schdeuler to render tasks in a more efficient way by scheduling . The fundamental that made it possible is the Fiber Architecture, which allows React to:

  • Split render tasks into smaller chunks for scheduling
  • Assign priority to different types of updates
  • Pause, abort, or reuse work when needed

Based on the above features, React Fiber has significantly improved the performance of React as component tree grows larger when UI gets more complicated over time. Check the Demo to visualize the performance difference of the Fiber architecture and the previous Stack-Based architecture.

Aside from enhancement in performance, responsiveness and UX, React Fiber is also the stepstone to other important features such as: React Hooks Suspense and Concurrent Mode.

🚄 Train of Thoughts

What problems does React Fiber intend to solve ?

Problems before React Fiber introduced include but not limited to:

  • Reconciler implementation is stack-based, such that render tasks must be done synchronously and can’t be paused or get back to where it got interrupted. This is bad when events with higher priority(e.g user input) get delayed causing bad user experience.
  • Complex render tasks might take longer time to complete and result in a lower frame rate.
  • Executing too much work all at once can block main thread due to JavaScripts single-thread nature.
  • Some works may be superseded by a more recent update and becomes unnecessary but can't be aborted.
  • Fetching data might leave React nothing to render before data is received, leaving React nothing to render.

These CPU-bounded and I/O-bounded issues both result in a poor user experience.

What approaches are introduced to tackle with aforementioned problems?

To solve aforementioned problems, two concepts are introduced in React: Time slicing to solve CPU issues and Suspense to solve I/O issues. Refer to Dan Abramov's talk for more in-depth concept. To carry out these concepts, reimplementation of the React reconcilier is needed.

Quoting from Andrew Clarks' note,

We've established that a primary goal of Fiber is to enable React to take advantage of scheduling. Specifically, we need to be able to

  • Pause work and come back to it later.
  • Assign priority to different types of work.
  • Reuse previously completed work.
  • Abort work if it's no longer needed.
  • In order to do any of this, we first need a way to break work down into units. In one sense, that's what a fiber is. A fiber represents a unit of work.

    -- Andrew Clark, React Fiber Architecture

    • Reconcilier is reimplemented with a linked-list-based strucutre, enabling Reconciliation process to be paused, aborted or returned after interrupted.

    • Introduction of Fiber:

      • Fiber represents an unit of work for React to process, which can be seen as React.Element extended with more properties.
      • Old reconciler creates an immutable tree of React.Element (also known as virtual DOM) to perform diff algorithm by traversing the tree recursively. New reconciler instead creates a tree of Fiber Node iteratively.
    • A Scheduler is introduced to schedule execution of tasks. Scheduler originally uses `window.requestIdleCallback(rIC)`` API for task scheduling, but the core team found some pitfalls such as:

      • Browser compatibility issue
      • rIC is not triggered in a stable manner if tab switching presents

      Thus, Scheduler is in fact a more rounded requestIdleCallback polyfill, which replaced the rIC with a postmessage loop with short intervals so that scheduling is not limited to align to frame boundaries. Refer to facebook/react issue #16214.

      After the scheduler is introduced, the steps of reconciliation process has an additional step.

    • Heuristic algorithm is implemented based on a few assumptions, that reduces the time complexity from O(n^3) to O(n).

    🕵️ Deep Dive

    What is Fiber?

    Broadly speaking, React Fiber is a complety rewrite of the reconciler to improve performance, but it is in fact a JavaScript object on a narrow scope which, refering to Andrew Clarks's note, contains information about a component, its input, and its output.

    ReactFiber.js (as of 2023/07)
    function FiberNode(
    this: $FlowFixMe,
    tag: WorkTag,
    pendingProps: mixed,
    key: null | string,
    mode: TypeOfMode,
    ) {
    // Instance
    this.tag = tag;
    this.key = key;
    this.elementType = null;
    this.type = null;
    this.stateNode = null;

    // Fiber
    this.return = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;

    this.ref = null;
    this.refCleanup = null;

    this.pendingProps = pendingProps;
    this.memoizedProps = null;
    this.updateQueue = null;
    this.memoizedState = null;
    this.dependencies = null;

    this.mode = mode;

    // Effects
    this.flags = NoFlags;
    this.subtreeFlags = NoFlags;
    this.deletions = null;

    this.lanes = NoLanes;
    this.childLanes = NoLanes;

    this.alternate = null;

    if (enableProfilerTimer) {
    // Note: The following is done to avoid a v8 performance cliff.
    //
    // Initializing the fields below to smis and later updating them with
    // double values will cause Fibers to end up having separate shapes.
    // This behavior/bug has something to do with Object.preventExtension().
    // Fortunately this only impacts DEV builds.
    // Unfortunately it makes React unusably slow for some applications.
    // To work around this, initialize the fields below with doubles.
    //
    // Learn more about this here:
    // https://github.com/facebook/react/issues/14365
    // https://bugs.chromium.org/p/v8/issues/detail?id=8538
    this.actualDuration = Number.NaN;
    this.actualStartTime = Number.NaN;
    this.selfBaseDuration = Number.NaN;
    this.treeBaseDuration = Number.NaN;

    // It's okay to replace the initial doubles with smis after initialization.
    // This won't trigger the performance cliff mentioned above,
    // and it simplifies other profiler code (including DevTools).
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
    }

    if (__DEV__) {
    // This isn't directly used but is handy for debugging internals:

    this._debugSource = null;
    this._debugOwner = null;
    this._debugNeedsRemount = false;
    this._debugHookTypes = null;
    if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
    Object.preventExtensions(this);
    }
    }
    }

    Crucial implementations are:

    • Instance Section

      • this.tag: Identify the type of the component, exhaust list can be seen here.
      • this.key: Key field that serves as an identifier to determine whether a fiber can be reused
      • this.elementType: Mostly same as type, but differ in some cases such as memoized component.
      • this.type: Identifies the type of DOM node, e.g div, span, ... etc..
      • this.stateNode: Points to the actual DOM node

      type describes the component that it corresponds to. For composite components, the type is the function or class component itself. For host components (divspan, etc.), the type is a string. Both type and key serve the same purpose as they do for React elements. In fact, when a fiber is created from an element, these two fields are copied over directly.

      Note that `type` is used in the heuristic algorithm, or the reconciliation, to speed up the whole compare progress. If `current fiber` and `work-in-progress fiber` of a given element has different `type`, reconciliation progress stops here and subtree with that exact element as root will be rerendered.

    • Fiber Section

      • These fields point to other fibers, describing the recursive and tree-like component structure -- a fiber tree.

        • this.return: Points to the fiber to which reconciler should return after processing the current one. It is conceptually the same as the previous stack frame in stack-based reconciler. If a fiber has multiple child fibers, each child fiber's return fiber is the parent.
        • this.child: Corresponds to the value returned by a component's render method. The child fibers form a singly-linked list whose head is the first child of the current fiber.
        • this.sibling: Accounts for the case where render returns multiple children, meaning components at the same level form a singly-linked list where each fiber points to the immediate next fiber.

        These three pointers form a linked-list structure which is the foundation of fiber architecture.

      • this.pendingProps & this.memoizedProps: A fiber's pendingProps are set at the beginning of its execution, and memoizedProps are set at the end. When the incoming pendingProps are equal to memoizedProps, it signals that the fiber's previous output can be reused, preventing unnecessary work.

      • this.alternate: At any time, a component instance is referred to by at most two fibers,

        • Current Fiber: Fiber that has been committed and rendered onto the screen.
        • Work-in-Progress: A fiber that has not yet been committed.

        The alternate of the current fiber is the work-in-progress, and the alternate of the work-in-progress is the current fiber.

        A fiber's alternate is created lazily using a function called createFiber. Instead of always creating a new object, createFiber uses a double buffering pooling technique that will attempt to reuse the fiber's alternate if it exists, which allows minimizing memory allocations.

    • Line 24~30 are utilized to memoize state of the fiber across rerenders.

    Lane Model

    To enable task scheduling, scheduler was first implemented in a expiration time model but replaced with the current lane model in 2020 to allow dynamic priority manipulating. More details to the initiative can be refered in this PR.

    How does Reconciler work ?

    React developers should be familiar with JSX and how React forms a virtual DOM based on JSX. Now with the fiber object, React performs an additional step by calling a createFiberFromTypeAndProps function to creates a fiber tree.

    Reconciler is responsible for identifing elements that need to be updated. During reconciliation, it can be divided into two phases: render phase and commit phase.

    To efficiently find out diff the current tree and work-in-progress tree, React implements a heuristic O(n) algorithm based on two assumptions:

    • Two elements of different types will produce different trees.
    • The developer can hint at which child elements may be stable across different renders with a key prop.

    In practice, these assumptions are valid for almost all practical use cases.

    Reconciliation details can be checked here.

    How does Scheduler work ?

    How does React process a fiber tree ?

    WIP

    child -> self -> sibling
    ❓ What happens within one frame?

    🔗 References