I recently read a good article called Build Your Own React. The article explains how React works from the basic core stages, but it feels like the original text is great but probably not “plain English” enough for beginners. Therefore, I plan to use the original text as the basis to make Chinese readers better understand. I will construct a Chinese version of React in a supplementary way, paying tribute to the original author.

Tutorials on the UI effects of the original article were also open source by the author. Make programming with Markdown ~ simple to make similar code display way! Great 👍

React 16.8 is a first-person narrative with a lot of crap built in for people who are not familiar with React or who have never used React before.

route

Although the links between these routes are not immediately obvious at first glance, you can read them in order, and each part will be explained.

  1. Pre –
  2. createElement
  3. render
  4. Concurrent Mode
  5. Fibers
  6. Render and Commit Phases
  7. Reconciliation
  8. Function Components
  9. Hooks

Configuration items

  1. Create a project using create-React-app. Although we won’t use most of these things, it meets our basic development criteria.
npx create-react-app mini-react
cd mini-react
npm/yarn start
Copy the code
  1. Let’s make some changes to the index.js file, which is the first step to get started.
import React from 'react';
import ReactDOM from 'react-dom';

const element = <h1 title="foo">Hello</h1>;
const container = document.getElementById("root");
ReactDOM.render(element, container);
Copy the code

Pre –

index.js

import React from 'react';
import ReactDOM from 'react-dom';

const element = <h1 title="foo">Hello</h1>;
const container = document.getElementById("root");
ReactDOM.render(element, container);
Copy the code

This is not a valid JS code, it uses babel-plugin-transform-react-jsx to convert from JSX to valid JS via Babel.

const element = <h1 title="foo">Hello</h1>Copy the code

Let’s take a look at the transformed code.

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

It replaces the DOM element with a call to the react. createElement method, and its data structure looks like this.

const element = {
  type: "h1".// Label type
  props: { 
    title: "foo"./ / dom attributes
    children: "Hello".// Nested content}},Copy the code

Now let’s try to bypass Babel and translate the code ourselves so that it can execute as it should.

Except the import part, there are three paragraphs in total, so we will translate them one by one.

import React from 'react';
import ReactDOM from 'react-dom';

const element = <h1 title="foo">Hello</h1>;
const container = document.getElementById("root");
ReactDOM.render(element, container);
Copy the code

Start by creating our elements, h1 and “Hello.”

const element = <h1 title="foo">Hello</h1>;

// Abstract it as a data structure
const element = {
  type: "h1".props: {
    title: "foo".children: "Hello",}}// Create h1 based on the contents of the structure
const node = document.createElement(element.type)
node["title"] = element.props.title

// Create an element and insert it from the child's data. Instead of selecting innerText, we want the child element to be a DOM element, with its attributes displayed
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
Copy the code

Next comes this step (well, he hasn’t changed! ~)

// const container = document.getElementById("root");

const container = document.getElementById("root");
Copy the code

Insert the element we created into root

//ReactDOM.render(element, container);

// node h1 tags, text "hello"
node.appendChild(text)
// Join our node
container.appendChild(node)
Copy the code

React is not used at all. See if it works. 😀 🎉 (Easy!)

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

createElement

Let’s look at the new code, which contains more complex nesting, and try to encapsulate the logic of the previous step. Let’s translate it again

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
Copy the code

First, let’s look at the code that frequently appeared in the previous step. We could have a function that takes the type of the node to be created, the properties of the node props, and the children element that can be included.

...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

The function looks like this

// We use the extended operator to get all subsequent children
function createElement(type, props,... children){
	// He returns our abstract data structure
	return {
    type.props: {
      ...props,
      children,
  }
}
Copy the code

Here we need to note that when the child element is not a tag, we can’t get its tag type, but we want all the tags inside children, so we need to do a conversion

function createElement(type, props,... children){
	return {
    type.props: {
      ...props,
		 // If it is not an object, it is a string element that needs to be labeled
		 children: children.map(child= >
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
  }
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT".props: {
      nodeValue: text,
      children: [],},}}Copy the code

Let’s replace this code with createElement and name our React MiniReact! (Here we only abstract the data structure, not render operation, please look back with questions ~)

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

To make our code more like a tripartite library, let’s restore the code and let babel-plugin-transform-react-jsx call our “translation method”! ~

...const MiniReact = {
  createElement,
}
 
// This is the babel-plugin-transform-react-jsx setup. See the link above to see all the Settings. This will call our createElement method in MiniReact.
/ * *@jsx MiniReact.createElement */
const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
Copy the code

In this step, we write the key createElement method in React, but we only create the target structure and do not render it. Next we will render it in render.

render

In this step we have a complete DOM structure tree, which contains the types, attributes, and child elements of each dom layer. Now we need to create and mount it using its description.

function render(element,container){
	// TODO creates a DOM node
}

// Add it to our MiniReact
const MiniReact = {
	createElement,
	render
}

MiniReact.render(element,container)
Copy the code

• Improved the logic for creating elements in Render (Element is the data structure for createElement, container is the DOM node)

...function render(element, container){
	const dom = document.createElement(element.type);
	// Perform the same render operation for each word element, rendering each of them, again, with the first argument for element data and the second argument for the target node to mount
	element.props.children.forEach(child= >Render (child, dom)) container. AppendChild (dom)}...Copy the code

Now we can make all kinds of nodes according to type, but we can’t! I’ve defined a TEXT_ELEMENT type for text, so let’s add some logic here to render to support this type.

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

So far we have only used type in the data structure (type/props/children), we also need to mount the props to the DOM we created.

function render(element, container) {
  const dom =
    element.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type)
 	// Because children also exist in props, we want to filter it
  const isProperty = key= >key ! = ="children"
  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name= > {
		 // Set the filtered props to dom
      dom[name] = element.props[name]
    })
 
  element.props.children.forEach(child= >
    render(child, dom)
  )
	container.appendChild(dom)
}
Copy the code

Now we can use our own MiniReact rendering to mimic JSX syntax. 🎉

But there is a small problem ❕❕❕

Now we will be synchronized to build the tree, and gradually create dom, set properties and mount, that if the tree is too large to do, he will take up js main bout time, unable to respond to user operation happens, how do we make it can suspend – to deal with higher priority event – continue to render?

React let’s see how React works and use our MiniReact.

concurrent mode

Concurrent mode can be understood in a single sentence to help applications remain responsive and adjust appropriately to the user’s device performance and network speed. This is explained as our part of the expectation to split the execution unit.

First we need a set of logic that can put the entire traversal work on pause or start. So we need to break tasks down into small units of work. After we finish one unitOfWork, we check to see if we have time to move on to the next, and if we do, we move on.

This can be done only if the browser alerts us if we have free execution time, which can be done through the requestIdleCallback method (later versions of React have replaced this with scheduler package). The callback passed in by this method is called when the browser is free, so let’s write the code.

let nextUnitOfWork = null
// The main logic for doing the loop work
function workLoop(deadline) {
  let shouldYield = false
  while(nextUnitOfWork && ! shouldYield) {// 2. PerformUnitOfWork is the main logic used to execute the unit of work. When it is finished, it returns to the next unit of work based on the sought logic
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
	  // 3. Deadline is the parameter given by the requestIdleCallback callback to check if there is any time left
    shouldYield = deadline.timeRemaining() < 1
  }
	// 4. If there is no operational unit of work or there is not enough time left, the callback will be set again and wait for the next browser call
  requestIdleCallback(workLoop)
}
// 1. Set the notification callback function
requestIdleCallback(workLoop)
// A method for processing units of work
function performUnitOfWork(nextUnitOfWork) {
  // TODO
}
Copy the code

We can now step through each unit of work, and when we receive a notification from the browser we can stop working and let the browser work on more important tasks, which is half the problem!

What is a unit of work?

fibers

There’s a DOM tree like this

  dom = <div>
    <h1>
      <P />
      <a />
    </h1>
    <h2 />
  </div>
	
	dom => root
Copy the code

It corresponds to the

So it’s easy to see that each of these cells is a DOM node, which contains some basic information

  • Type (type)
  • Properties (props)
    • Children
    • Valid properties, specific Settings on the node (… Props)

So for now we can call the unit of work Fiber and vice versa.

So let’s just remember what we did.

  1. Write the render method, which is the initial step of rendering. After receiving the dom describing structure and target node, create elements layer by layer and add to the parent node to complete all parts.
  2. The execution process that can be interrupted is set up, conditional on the needThere's the next unit of workwithEnough time to execute the mission.

Careful observation shows that there is no way to stop the first step, so we need to modify the Render method so that it can work with the second step.

The original method

...function render(element, container) {
  const dom =
    element.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type)
  const isProperty = key= >key ! = ="children"
  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name= > {
      dom[name] = element.props[name]
    })
 
  element.props.children.forEach(child= >Render (child, dom)) container. AppendChild (dom)}...Copy the code

First, the logic to create the node is taken out. There are two things we didn’t write here, which we will deal with later.

  1. Logic for recursive child elements
  2. Inserts child elements into parent elements
...// Its responsibility is very simple, receiving the data of the unit of work (fiber) to generate the DOM
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 render(element, container) {
  // TODO sets the next unit of work to be processed}...Copy the code

All Render needs to do now is just start the mission

function render(element, container) {
	// After setting up the next unit of work, the browser will notify us when it is free, so we check to see if there is a next unit of work available and if we have enough time, we start the task!
  nextUnitOfWork = {
    dom: container,
    props: {
      children: [element],
    },
  }
}
 
let nextUnitOfWork = null
Copy the code

The task begins. We need to solve a new problem and two minor ones that we just ignored.

  1. Who calls createDom?
  2. Logic for recursive child elements
  3. Inserts child elements into parent elements

All of this logic is done in functions that handle units of work.

function performUnitOfWork(fiber){
	// TODO creates/adds DOM elements
	// TODO creates a new fiber element
	// TODO return next unit of work
}
Copy the code

Let’s each complete the three steps in FormUnitofWork.

  1. Create/Add DOM elements (call createDom to insert child elements)
function performUnitOfWork(fiber) {
	// We may traverse the same fiber tree many times, so create the DOM when there is no DOM here for the first time
  if(! fiber.dom) { fiber.dom = createDom(fiber) }// If there is a parent node, it can be inserted
  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
 
	// TODO creates a new fiber element
	// TODO return next unit of work
}
Copy the code
  1. Create a new Fiber element
function performUnitOfWork(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber) }if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
 
	/* * Element has a similar structure. * Element = {* type: "p", * props:{* children:[] *} * 
	const elements = fiber.props.children
  let index = 0
  let prevSibling = null
 
  while (index < elements.length) {
    const element = elements[index]
 	  // Convert the original data to fiber
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,}// The parent fiber is associated only with the first child fiber
	  // But all child fibers can find the parent fiber
    if (index === 0) {
      fiber.child = newFiber
    } else {
		 // All other fibers point to the sibling fiber
      prevSibling.sibling = newFiber
    }
 
    prevSibling = newFiber
    index++
  }

	// TODO return next unit of work
}
Copy the code
  1. Return Next unit of work, the selected priorities are respectively
    1. Check if there are sub-fibers
    2. Check if there is a brother Fiber
    3. Check if there is a sibling of the parent Fiber

function performUnitOfWork(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber) }if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
 
	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 there are subfibers, return subfibers
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
	  // Return sibling fiber if there is sibling fiber
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
	  // When there are no more sibling fibers, return the sibling of the parent fiber and repeat
    nextFiber = nextFiber.parent
  }
}
Copy the code

Done! At this point we have completed the refactored code and can now not only render the created elements, but also interrupt execution and continue rendering. 🎉

However, we ran into another problem. We noticed that in this method, every time a node was created, an insert was performed, which caused a very strange situation when the task was interrupted and stopped. Some elements were partially rendered and not completely completed.

...if(fiber. The parent) {fiber. The parent. The dom. The appendChild (fiber. The dom)}...Copy the code

So we need to adjust the code so that fiber creation and DOM rendering can be done from start to finish in a working DOM tree. We need to distinguish between the two stages of render and the working node calculation, and commit is the final rendering process.

render/commit phases

First, we need to delete this code so that it doesn’t modify the DOM at every step.

...if(fiber. The parent) {fiber. The parent. The dom. The appendChild (fiber. The dom)}...Copy the code

Back to render, create root data for our work (wip root = work in Progress root)

function render(element, container) {
	// Store the entire wipRoot
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  }

	// wipRoot was the first unit of work
  nextUnitOfWork = wipRoot
}
 
let nextUnitOfWork = null
let wipRoot = null
Copy the code

Then add a commit phase

... Function commitRoot(){// TODO add node to dom}...Copy the code

Modify the execution timing of the commit in the workLoop function

... function workLoop(deadline) { let shouldYield = false while (nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork ) shouldYield = deadline.timeRemaining() < 1 } // Render is committed when there is no next working node and wipRoot exists. NextUnitOfWork &&wiproot) {+ commitRoot() +} requestIdleCallback(workLoop)}...Copy the code

Now that we’ve separated the render and commit phases, we need to refine the commit logic!

reconciliation

There are two rules to be aware of at this stage (heuristic algorithm for React)

  1. Two elements of different types will produce different trees
  2. The developer can use the key to indicate which child elements remain the same in each render (we didn’t implement that in this example)
function commitRoot() {
	// We pass in the first subfiber as we do for the next working node
  commitWork(wipRoot.child)
	// To prevent the commit phase from happening more than once, clear wipRoot references here
  wipRoot = null
}

function commitWork(fiber) {
  if(! fiber) {return
  }
	// Find the parent node and insert the child element
  const domParent = fiber.parent.dom
  domParent.appendChild(fiber.dom)
	// Recursively render child and sibling nodes
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}
Copy the code

We only dealt with adding the DOM above, now we need to modify and remove nodes from it according to some rules.

But again, we can’t operate on the work node while iterating through it before modifying or deleting it, so we need to mark the unit of work and then operate it.

Modify the commitRoot method

let currentRoot = null; 
function commitRoot() {
  commitWork(wipRoot.child)
  + currentRoot = wipRoot
  wipRoot = null
}
Copy the code

Modify render method


function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
	  CurrentRoot will be assigned to currentRoot when the commit is completed
	  // So alternate here will be the wipRoot from the last commit
	  // We use it for comparison! ~
    + alternate: currentRoot,
  }
  nextUnitOfWork = wipRoot
}
Copy the code

Next, let’s split up the way we handle unit of work

function performUnitOfWork(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber) }const elements = fiber.props.children
	// encapsulate the logic to create sub-fiber in this function
  reconcileChildren(fiber, elements)
 
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

// Create a sub-fiber
function reconcileChildren(wipFiber, elements) {
  let index = 0
  let oldFiber =
    wipFiber.alternate && wipFiber.alternate.child
  let prevSibling = null
 
  while( index < elements.length || oldFiber ! =null
  ) {
	  // Switch to the next fiber
    const element = elements[index]
    let newFiber = null

    const sameType =
      oldFiber &&
      element &&
      element.type == oldFiber.type
 
    if (sameType) {
      // TODO updates the node
    }
    if(element && ! sameType) {// TODO adds a new node
    }
    if(oldFiber && ! sameType) {// TODO removes the old Fiber and node
    }
 
    // TODO uses old Fiber to compare to current Element data
 
    if (oldFiber) {
		 // The same old fiber will switch to the next one
      oldFiber = oldFiber.sibling
	    // Create a new fiber for the child element
    	 const newFiber = {
      	type: element.type,
      	props: element.props,
      	parent: wipFiber,
      	dom: null,}// The subsequent operations are the same...
    	 if (index === 0) {
      	wipFiber.child = newFiber
    	 } else {
      	prevSibling.sibling = newFiber
    	 }
 
	     prevSibling = newFiber
	     index++
    }
}
Copy the code

In the middle, we will link each unit of work, connect the old unit of work to the current unit of work, and mark the current unit of work when comparing. The process of comparison follows several rules.

  1. Compare the two types, if they are the same then just UPDATE the DOM property (props) “UPDATE”
...// TODO updates the node
	  if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE",}}...Copy the code
  1. If the type is different and there is a new element, that means we need to create and replace a new DOM “PLACEMENT”
      // TODO adds a new node
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null.parent: wipFiber,
        alternate: null.effectTag: "PLACEMENT",}Copy the code
  1. If the type is different and there are old elements, we need to delete the old “DELETION”.

At this step we don’t have a new fiber, so we add the tag on top of the old fiber, and we don’t iterate over the old fiber tree at commit time, so here we put it in an array to be deleted, and then delete it by iterating through the DOM in the array.

      // TODO removes the old Fiber and node
		 oldFiber.effectTag = "DELETION" deletions. Push (oldFiber)Copy the code

At the same time we need to add a global variable deletions and empty the array on the first render

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  }
  + deletions = []
  nextUnitOfWork = wipRoot
}
 
let nextUnitOfWork = null
let currentRoot = null
let wipRoot = null
+ let deletions = null
Copy the code

Finally, we need to perform the corresponding operations for each type (“UPDATE”/”PLACEMENT”/”DELETION “), which is done in commitWork.

First, execute commitWork separately for the fiber to be deleted.

function commitRoot(){
	+ deletions.forEach(commitWork);
	commitWork(wipRoot.child);
	currentRoot = wipRoot;
	wipRoot = null;
}
Copy the code

Perfect commitWork logic.

function commitWork(fiber) {
  if(! fiber) {return
  }
  const domParent = fiber.parent.dom
	
	// Replace operation
  if (
    fiber.effectTag === "PLACEMENT"&& fiber.dom ! =null
  ) {
    domParent.appendChild(fiber.dom)
	// Update operation
  } else if (
    fiber.effectTag === "UPDATE"&& fiber.dom ! =null
  ) {
	  // When updating the DOM we need to pass in the old props to compare the props
    updateDom(
      fiber.dom,
      fiber.alternate.props,
      fiber.props
    )
	// Delete operation
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom)
  }
 
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}  


// Whether it is an event
const isEvent = key= > key.startsWith("on")
// Check if it is valid props
const isProperty = key= >key ! = ="children" && !isEvent(key)
// Is the new props
const isNew = (prev, next) = > key= >prev[key] ! == next[key]// Whether he is removed in the new props
const isGone = (prev, next) = > key= >! (keyin next)
function updateDom(dom, prevProps, nextProps) {
	// Remove the old listener
	Object.keys(prevProps)
    .filter(isEvent)
    .filter(
      key= >! (keyin nextProps) ||
        isNew(prevProps, nextProps)(key)
    )
    .forEach(name= > {
      const eventType = name
        .toLowerCase()
        .substring(2)
      dom.removeEventListener(
        eventType,
        prevProps[name]
      )
    })
 
  // Delete the old props
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach(name= > {
      dom[name] = ""
    })
 
  // Add new props and modify old props
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name= > {
      dom[name] = nextProps[name]
    })

	 
  // Add a new listener event
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach(name= > {
      const eventType = name
        .toLowerCase()
        .substring(2)
      dom.addEventListener(
        eventType,
        nextProps[name]
      )
    })
}
Copy the code

Here we have fully implemented a MiniReact of our own. It has all the essentials, but it does not yet support functional components and Hooks that are specific to functional components. ~ 🎉

function components

Let’s start with fiber. Up until now we’ve been using the node type that the Babel plugin returned to us, which is a string. But how do we know what type a function component is?

fiber = {
	type: "XXX",props: {},dom:null,... }Copy the code

Function components differ from the previous writing in two ways

  1. Fiber does not have a DOM node (we can’t get its type from the function, and then create the DOM and write fiber)
  2. Children come from the runtime function, not directly from props (previously we could parse the syntax directly using the Babel plugin)

An example follows…

/ * *@jsx MiniReact.createElement */
function App(props) {
  return <h1>Hi {props.name}</h1>
}
const element = <App name="foo" />
const container = document.getElementById("root")
MiniReact.render(element, container)

Copy the code

We need to modify performUnitOfWork, the function that performs the unit of work

function performUnitOfWork(fiber) {
	// First pull out the method that creates the DOM directly, because createDom does not work on function components
  if(! fiber.dom) { fiber.dom = createDom(fiber) }constElements = fiber. Props. Children reconcileChildren(Fiber, Elements)... }Copy the code

function performUnitOfWork(fiber) {
  const isFunctionComponent =
    fiber.type instanceof Function
	// Here we make special treatment for function types, non-function types perform logic invariant
  if (isFunctionComponent) {
    updateFunctionComponent(fiber)
  } else} {updateHostComponent (fiber)... }function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)]
	// Up to this point our processing logic is the same as before
  reconcileChildren(fiber, children)
}
 
function updateHostComponent(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber) } reconcileChildren(fiber, fiber.props.children) }Copy the code

Modify the commitWork file

function commitWork(fiber) {
  if(! fiber) {return
  }
 
	// There is no way to guarantee that every fiber has a DOM, so we need to change it here
  letDomParentFiber = fiber. The parent. The dom... }Copy the code

To find the parent of the DOM node, we need the upper-layer fiber until we find the fiber with the DOM

function commitWork(fiber) {
  if(! fiber) {return
  }

	let domParentFiber = fiber.parent
  while(! domParentFiber.dom) { domParentFiber=domParentFiber.parent }constDomParent = domParentFiber. Dom... }Copy the code

In the same way, we need to go layer by layer to fiber with the DOM

function commitWork(fiber){...// Delete operation
  } else if (fiber.effectTag === "DELETION"{-domparent. RemoveChild (fiber. Dom) + commitDeletion(fiber, domParent)}... }function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child, domParent)
  }
}
Copy the code

Then we are fully compatible with the case of function components! 😛

hooks

Finally, now that we have the function component, we should also add Hooks to it to add state.

Now let’s change the example to a counter component that will add state + 1 every time we click on it.

...constMiniReact = {... , useState }function Counter() {
  const [state, setState] = MiniReact.useState(1)
  return (
    <h1 onClick={()= > setState(c => c + 1)}>
      Count: {state}
    </h1>)}const element = <Counter />Copy the code

Before each useState call, we need to initialize some global variables in the updateFunctionComponent method so that we can use them inside the useState function.

let wipFiber = null
let hookIndex = null
 
function updateFunctionComponent(fiber) {
	// Save the old fiber for later use to find the old hook
  + wipFiber = fiber
	// Marks the current number of hook executions. Each execution of this method represents an update, even if hookIndex is cleared
  + hookIndex = 0
	// Empty the old Fiber hook
  + wipFiber.hooks = []
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}
Copy the code
function useState(initial) {
	// Bring up the old Fiber hook
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex]

	// useState state is not reset every time render is performed
	// We can retrieve the last state from the old hook and reuse it
  const hook = {
    state: oldHook ? oldHook.state : initial,
	  // Each time setState is set to an action, all actions will exist here
    queue: [],}// Every time we render, we get all the unexecuted actions and perform an update state
  const actions = oldHook ? oldHook.queue : []
  actions.forEach(action= > {
    hook.state = action(hook.state)
  })
 
	// For each update, we save an event and push it into the hook queue of the unit of work
	// And set the current fiber with the old fiber (current unit of work currentRoot).
  const setState = action= > {
    hook.queue.push(action)
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot,
    }
	  // Set the next unit of work, indicating that the browser is free to start work, perform the update logic, let the state update
    nextUnitOfWork = wipRoot
    deletions = []
  }
 
	// Add a hook to the old fiber
  wipFiber.hooks.push(hook)
	// subscript +1 to ensure that we get the right hook every time
  hookIndex++
  return [hook.state, setState]
}
Copy the code

If the article is found to be inadequate, I hope you point out.

Thanks for the Build Your Own React inspiration.