Rewrite React Fiber core algorithm in 300 lines
Tags: react, javascript
Published: 2021-06-21 05:24:52


Rewrote the React Fiber algorithm.

{% github okmttdhr/my-own-react no-readme %}

It's about 300 lines long, but I've tried to re-implement the real React as closely as possible, including the naming and function's scope. The implementation was focused on the following points;

- The data structure and algorithm of Fiber
- An interruptible unit of work (conceptually same as concurrent mode)
- Render and commit phase

Architecture;

![architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lo423dw2egteuwi13xpi.png)

I have also commented a link to the React source code, so you can compare it for a better understanding. (For example, in this case, `requestIdleCallback` is used to implement pseudo-scheduling, but React is using its own Scheduler).

Since there is already a lot of information about the architecture of Fiber, this article will only introduce some pieces of code.

### performUnitOfWork

Traverse the Fiber to determine the next unit of work.

```js
// https://github.com/okmttdhr/react/blob/84c06fef8168e779d15cc9450f67888445f7b4f4/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L3206
function beginWork(fiber) {
  const isFunctionComponent =
    fiber.type instanceof Function
  if (isFunctionComponent) {
    updateFunctionComponent(fiber)
  } else {
    updateHostComponent(fiber)
  }

  if (fiber.child) {
    return fiber.child
  }

  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

// https://github.com/facebook/react/blob/84c06fef8168e779d15cc9450f67888445f7b4f4/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L1574
function performUnitOfWork(fiber) {
  nextUnitOfWork = beginWork(fiber)
  if (!nextUnitOfWork) {
    commitRoot()
    completeUnitOfWork()
  }
}
```

### workLoop

Loop until it runs out of work.
When the browser gets busy, stop the loop and come back when it's done.

```js
// https://github.com/facebook/react/blob/84c06fef8168e779d15cc9450f67888445f7b4f4/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L1567
function workLoop(deadline) {
  let shouldYield = false
  while (workInProgressRoot && !shouldYield) {
    performUnitOfWork(nextUnitOfWork)
    shouldYield = deadline.timeRemaining() < 1
  }
  requestIdleCallback(workLoop)
}
```

### reconcileChildren

Diffing and updating Fiber

```js
// https://github.com/facebook/react/blob/84c06fef8168e779d15cc9450f67888445f7b4f4/packages/react-reconciler/src/ReactFiberBeginWork.new.js#L255
function reconcileChildren(workInProgressFiber, elements) {
  let index = 0
  let oldFiber =
    workInProgressFiber.alternate && workInProgressFiber.alternate.child
  let prevSibling = null

  while (
    index < elements.length ||
    oldFiber != null
  ) {
    const element = elements[index]
    let newFiber = null

    const sameType =
      oldFiber &&
      element &&
      element.type == oldFiber.type

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: workInProgressFiber,
        alternate: oldFiber,
        flag: UPDATE,
      }
    }
    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: workInProgressFiber,
        alternate: null,
        flag: PLACEMENT,
      }
    }
    if (oldFiber && !sameType) {
      oldFiber.flag = DELETION
      deletions.push(oldFiber)
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling
    }

    if (index === 0) {
      workInProgressFiber.child = newFiber
    } else if (element) {
      prevSibling.sibling = newFiber
    }

    prevSibling = newFiber
    index++
  }
}
```

### commitWork

Update DOM (Commit phase)

```js
// https://github.com/facebook/react/blob/84c06fef8168e779d15cc9450f67888445f7b4f4/packages/react-reconciler/src/ReactFiberCommitWork.new.js#L1814
function commitWork(fiber) {
  if (!fiber) {
    return
  }

  let parentFiber = fiber.parent
  while (!parentFiber.dom) {
    parentFiber = parentFiber.parent
  }
  const parentDom = parentFiber.dom

  if (
    fiber.flag === PLACEMENT &&
    fiber.dom != null
  ) {
    commitPlacement(fiber, parentDom)
  } else if (
    fiber.flag === UPDATE &&
    fiber.dom != null
  ) {
    commitUpdate(
      fiber.dom,
      fiber.alternate.props,
      fiber.props
    )
  } else if (fiber.flag === DELETION) {
    commitDeletion(fiber, parentDom)
  }

  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

// https://github.com/facebook/react/blob/84c06fef8168e779d15cc9450f67888445f7b4f4/packages/react-reconciler/src/ReactFiberWorkLoop.new.js#L1693
function commitRoot() {
  deletions.forEach(commitWork)
  commitWork(workInProgressRoot.child)
}
```

Comments and PRs are welcome!

### Resources

- https://reactjs.org/docs/codebase-overview.html#fiber-reconciler
- https://github.com/facebook/react/issues/7942
- https://github.com/pomber/didact