Handwritten react-fiber architecture how to interrupt and recover

/** @jsxRuntime classic */

//**================ */

const Didact = {
  createElement,
  render,
};

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children. map((child) =>
        typeof child === "object" ? child : createTextElement(child)
      ),
    },
  };
}

/** @jsx Didact. createElement */
const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
);
// const element = Didact.createElement(
// "div",
// { id: "foo" },
// Didact.createElement("a", null, "bar"),
// Didact. createElement("b")
// );

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ?document.createTextNode("")
      : document.createElement(fiber.type);

  const isProperty = (key) => key !== "children";
  Object.keys(fiber.props)
    .filter(isProperty)
    .forEach((name) => {
      dom[name] = fiber. props[name];
    });

  return dom;
}

function commitRoot() {
  commitWork(wipRoot. child);
  wipRoot = null;
}

function commitWork(fiber) {
  if (! fiber) {
    return;
  }
  const domParent = fiber. parent. dom;
  domParent.appendChild(fiber.dom);
  commitWork(fiber.child);
  commitWork(fiber. sibling);
}

let nextUnitOfWork = null;
let wipRoot = null;
let currentRoot = null;

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork & amp; & amp; !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline. timeRemaining() < 1;
  }
  if (!nextUnitOfWork & amp; & amp; wipRoot) {
    commitRoot();
  }
  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

// Get the first node of the work, execute the work, and return the next unit of work
function performUnitOfWork(fiber) {
  if (! fiber. dom) {
    fiber.dom = createDom(fiber);
  }
  const elements = fiber.props.children;
  let index = 0;
  let prevSibling = null;
  while (index < elements. length) {
    const element = elements[index];
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    };
    if (index === 0) {
      fiber.child = newFiber;
    } else {
      prevSibling. sibling = newFiber;
    }
    prevSibling = newFiber;
    index + + ;
  }
  if (fiber. child) {
    return fiber. child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber. sibling) {
      return nextFiber. sibling;
    }
    nextFiber = nextFiber. parent;
  }
}

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  };
  nextUnitOfWork = wipRoot;
}

const container = document. getElementById("root");
Didact. render(element, container);

React 17 version began to provide a new JSX conversion scheme. Before, JSX code would be compiled into React.createElement(...) by default, but after using the new JSX solution, it would be compiled from an independent module react/jsx-runtime Introduce JSX functions.

babel plugin @babel/plugin-transform-react-jsx

/** @jsxRuntime classic */ babel runtime transformation

render creates the initial fiber object, and after the render is executed, the workLoop method will be executed to develop the fiber task

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  };
  nextUnitOfWork = wipRoot;
}

If there is a next fiber task, execute performUnitOfWork. If it does not indicate that the fiber tree is built, execute commitRoot to submit the page for rendering.

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork & amp; & amp; !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline. timeRemaining() < 1;
  }
  if (!nextUnitOfWork & amp; & amp; wipRoot) {
    commitRoot();
  }
  requestIdleCallback(workLoop);
}

performUnitOfWork builds the fiber tree and returns the next fiber task

//Get the first node of the work, execute the work, and return the next work unit
function performUnitOfWork(fiber) {
  if (! fiber. dom) {
    fiber.dom = createDom(fiber);
  }
  const elements = fiber.props.children;
  let index = 0;
  let prevSibling = null;
  while (index < elements. length) {
    const element = elements[index];
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    };
    if (index === 0) {
      fiber.child = newFiber;
    } else {
      prevSibling. sibling = newFiber;
    }
    prevSibling = newFiber;
    index + + ;
  }
  if (fiber. child) {
    return fiber. child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber. sibling) {
      return nextFiber. sibling;
    }
    nextFiber = nextFiber. parent;
  }
}

The fiber object is hooked to the real dom object.

 if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }

Process the child elements of the fiber object, and create fiber objects for the corresponding child elements. At this time, the dom attribute of the fiber object of the child elements is still empty. If it is the first child element, then hang the fiber object of the first child element to the child attribute of the parent fiber object. If it is not the first child element of the parent fiber, then hang the fiber object to the sibling attribute of the previous fiber object. (That is, only the fiber object corresponding to the direct child element of the parent fiber is created, but the fiber task corresponding to the child element has not been processed yet)

let index = 0;
  let prevSibling = null;
  while (index < elements. length) {
    const element = elements[index];
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,
    };
    if (index === 0) {
      fiber.child = newFiber;
    } else {
      prevSibling. sibling = newFiber;
    }
    prevSibling = newFiber;
    index + + ;
  }

Returns the next fiber task, processing the first child if any. If there is no child element, then process its sibling node, if there is no sibling node, then return the parent node. Then look for sibling nodes of the parent element until the initial node is found. When performUnitOfWork has no nodes that can be returned, it means that the fiber tree is built and the commitRoot is executed to render the page. (Each fiber task can be interrupted after processing. If it is resumed, it will continue to process nextUnitOfWork. Therefore, the termination and recovery of fiber tasks is in the fiber tree construction stage)

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

Page rendering:

commitWork(wipRoot.child); //Start from the outermost layer

wipRoot = null;//Start rendering to set wipRoot to null, because it will be rebuilt next time the page is refreshed.

const domParent = fiber. parent. dom;

domParent.appendChild(fiber.dom);

commitWork(fiber.child);

commitWork(fiber. sibling);

Add child dom nodes to the parent dom by recursively. Finally finished rendering. (It cannot be interrupted at this time. In fact, interruption can also be implemented here, but it is not implemented here. The principle is the same as above. All processing is bound to an object, and interruption and recovery can be realized.)

function commitRoot() {
  commitWork(wipRoot. child);
  wipRoot = null;
}

function commitWork(fiber) {
  if (! fiber) {
    return;
  }
  const domParent = fiber. parent. dom;
  domParent.appendChild(fiber.dom);
  commitWork(fiber.child);
  commitWork(fiber. sibling);
}