React (React) : React (React) : React (React) : React (React)

The main purpose of this article is to write a simple React step-by-step architecture that follows the React code, but without all the optimizations and non-essential functionality.

The process is divided into the following steps:

  1. The createElement Function
  2. The render Function
  3. Concurrency model
  4. Fibers
  5. Render and commit phases
  6. coordinate
  7. Function component
  8. Hooks

To prepare

First, JSX syntax is converted to JavaScript through Babel, so we need to configure Babel in our project.

Installation:

npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react
Copy the code

Add. Babelrc to the root directory:

{
    "presets": ["@babel/preset-env", "@babel/preset-react"]
}
Copy the code

.babel.config.js

Const presets = [["@babel/env", {targets: {edge: "17", Firefox: "60", Chrome: "67", Safari: "11.1",}, useBuiltIns: "Usage ", corejs: "3.6.4",},],]; module.exports = { presets };Copy the code

Run NPX SRC –out-dir lib to save the conversion results in the lib folder

Zero: to review

The following code shows how to render a React component:

Const element = <h1 title="foo">Hello</h1> // Define React component const container = document.getelementById ("root") // Container reactdom. render(Element, container) // Render the React component into the containerCopy the code

To implement React, we first need to know how the React method is implemented, how to write it in native JavaScript, and then encapsulate it into our method. Now, let’s do this without using the React syntax.

  1. Define the components

The rule for Babel to convert JSX is to replace the code inside <> with a call to createElement, passing tag name, props, and child elements as arguments:

const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)
Copy the code

React.createElement does some validation on the argument and returns an object that looks like this:

const element = {
  type: "h1",
  props: {
    title: "foo",
    children: "Hello",
  },
}
Copy the code

So we can simply replace the function call with this object. The Type attribute is a string indicating the type of the DOM node, which is the tagName that will be passed to Document.CreateElement when the HTML element is generated.

The props property is an object that contains all the key-value pairs of the JSX property, with a special property: children:

In this example, children is a string, but it is usually an array containing multiple elements. So the real structure of the element.

  1. Apply colours to a drawing

(The container is obtained by calling Document.getelementById directly.) The other React code we’ll replace is the call to reactdom.render. Render is where React modifies the DOM, so we’ll implement DOM updates here.

First we create a node *, h1 in this case, with the type attribute. We then assign the element’s attribute to the node, in this case the title attribute.

const node = document.createElement(element.type)
node["title"] = element.props.title
Copy the code

* To avoid confusion, “elements” are used to refer to React elements and “nodes” to refer to DOM nodes.

Next we create child nodes, in this case just one text node. Using textNode instead of innerText allows us to process all elements the same way (for easy recursion). Note that we set nodeValue: {nodeValue: “hello”} for the text node as we did for the title of h1.

const text = document.createTextNode("")
text["nodeValue"] = element.props.children
Copy the code

Finally, we add textNode to h1, which we then add to the Container.

This allows us to implement the same functionality without using React. React React React React React React React React

const element = {
  type: "h1",
  props: {
    title: "foo",
    children: "Hello",
  },
}
​
const container = document.getElementById("root")
​
const node = document.createElement(element.type)
node["title"] = element.props.title
​
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
​
node.appendChild(text)
container.appendChild(node)
Copy the code

1:createElementfunction

(In the previous step, we defined the Element object directly.) Now it’s time to actually implement a createElement function of our own. Let’s examine how createElement is called by converting JSX to JS:

const element = React.createElement(
  "div",
  { id: "foo" },
  React.createElement("a", null, "bar"),
  React.createElement("b")
)
Copy the code

Remember the Element object we defined in the previous step? Our function should create an object with properties of type and props:

function createElement(type, props, ... children) { return { type, props: { ... props, children, }, } }Copy the code

We use the expansion operator for props and the remaining arguments for children, because we want the children property to be an array. For example, createElement(“div”) returns

{
  "type": "div",
  "props": { "children": [] }
}
Copy the code

CreateElement (“div”, null, a) returns

{
  "type": "div",
  "props": { "children": [a] }
}
Copy the code

CreateElement (“div”, null, a, b) returns

{
  "type": "div",
  "props": { "children": [a, b] }
}
Copy the code

The Children array may contain raw values such as strings or numbers (leaf nodes in the tree). So we should wrap elements that are not objects and give them a special attribute: TEXT_ELEMENT.

React does not wrap raw values or create empty arrays when there are no child elements, but we prefer to simplify the code rather than pursue code performance 😀.

To replace React, we’ll call our library Didact. But we still want to use JSX syntax. How do we tell Babel to use Didact createElement instead of React?

With a comment like this, when Babel compiles JSX, it uses the function we defined:

/** @jsx Didact.createElement */
const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
Copy the code

2.renderfunction

Next, we’ll write our own reactdom.render function. For now, all we care about is adding things to the DOM, and we’ll do updates and deletions later.

We first generate a DOM node based on the Element Type and then add the new node to the Container.

We recursively perform the same operation on each child node. Don’t forget the text element, if the element type was TEXT_ELEMENT we would create a text node instead of a regular node:

function render(element, container) {
  const dom =
    element.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type)
​
  element.props.children.forEach(child =>
    render(child, dom)
  )
Copy the code

Finally, we need to assign element attributes to the node:

const isProperty = key => key ! == "children" Object.keys(element.props) .filter(isProperty) .forEach(name => { dom[name] = element.props[name] })Copy the code

Done! We have implemented a library to render JSX into the DOM.

3. Concurrency model

We need to refactor the code before we can do more.

Because there’s a problem with our recursive call. Once we start rendering, we won’t stop until we’ve rendered the entire Element tree. If the element tree is large, it may block the main thread for a long time. And if the browser needs to implement a smooth animation or handle a high-priority task like user input, it will wait until the rendering is complete.

So we’ll break up the work into smaller units, and every time we finish a unit, we’ll ask the browser to interrupt the rendering if there’s something else we need to do.

let nextUnitOfWork = null function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && ! ShouldYield) {// There is another cell in the task list and there is still free time Deadline. TimeRemaining () < 1 // Determine whether there is free time} requestIdleCallback(workLoop)} requestIdleCallback(workLoop) function performUnitOfWork(nextUnitOfWork) { // TODO }Copy the code

We use requestIdleCallback to implement the loop. You can think of reuqestIdleCallback as a setTimeout, except instead of telling it when to run, the browser will run the callback when the main thread is idle.

React no longer uses requestIdleCallback, instead the Scheduler package is used, but in this case the concept is the same.

RequestIdleCallback also gives us a deadline argument. That’s how we use it to determine how much time we have before the browser takes control.

We start the loop by setting the first cell in the task, and then write a performUnitOfWork function that executes the task and returns the next cell in the task.

4.Fibers

To organize all the units in a task we need a data structure: a Fiber tree.

Each element has a corresponding fiber and each fiber is a unit in the task.

Let’s look at an example. Suppose we want to render an Element tree like this:

Didact.render(
  <div>
    <h1>
      <p />
      <a />
    </h1>
    <h2 />
  </div>,
  container
)
Copy the code

In Render we will create a root fiber and set it to Next Tone of work. The next task will be performed in FormUnitofWork and we will do three things for each fiber:

  1. Add elements to the DOM
  2. Created for the child of the elementfibers
  3. Select the next task

(I don’t know if you noticed, but it’s still recursion, but we didn’t render every node in recursion without thinking!)

Those of you who have studied data structures should know what a tree looks like:

Just like the long fiber tree! 😀 This data structure is chosen for traversal. Each fiber is linked to its first child node, its adjacent siblings, and its parent node.

When we complete a task on fiber, if it has a child node, then fiber will be the next unit to execute the task. For example, when we finish tasks on DivFiber, the next unit will be H1Fiber.

If a fiber has no child, we use Sibling as the next unit of the task. For example, pFiber doesn’t have a Child, so we came to AFiber after it was done.

If a fiber has neither child nor sibling, we find its “uncle” : parent’s Sibling. Like the relationship between AFiber and H2Fiber.

Of course, if parent does not have Sibling either, we will follow parents until we find a fiber with Sibling or reach root. If we get to root, it means we’ve done all of render’s tasks. (Those familiar with data structures must be very happy! This is the process of root traversal 😄)

Let’s implement the code part:

In the render function we set the root of the Fiber Tree to nextUnitOfWork

function render(element, container) {
  nextUnitOfWork = {
    dom: container,
    props: {
      children: [element],
    },
  }
}
Copy the code

Next, when the browser is ready, it will call back our workLoop and we will start the task from root. First, we create a new node and add it to the DOM. We use the fiber.dom attribute to track the DOM node:

function performUnitOfWork(fiber) { if (! fiber.dom) { fiber.dom = createDom(fiber) } if (fiber.parent) { fiber.parent.dom.appendChild(fiber.dom) } // TODO create  new fibers // TODO return next unit of work }Copy the code

Next we create a new fiber for each child:

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,
    }
  }
Copy the code

Then we add it to the Fiber Tree as a child or sibling, depending on whether it’s the first child:

if (index === 0) {
      fiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }
​
    prevSibling = newFiber
    index++
Copy the code

Finally we look for the next unit in the task. We first look for child, then Sibling, then uncle, and so on.

 if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
Copy the code

This is our performUnitOfWork spicy!