1. Achieving overall goals

Write a quick version of React to see what Fiber architecture does. Thus, we have an intuitive understanding of the basic principle of React.

Learning advice: download this section of the code, compared to the article view, as far as possible hands-on implementation again;

The implementation is version 16.8, based on pomb.us/build-your-… ;

The objectives are as follows:

  1. The createElement method;
  2. Render.
  3. Concurrent mode;
  4. Fibers;
  5. Render and submit;
  6. Coordination;
  7. Function component;
  8. Hooks;
  9. Class components

Implement createElement

Learning advice: download the code in this section, check the article, and try to implement it again.

1, the preface

Before React17, we used to introduce React code. If we didn’t introduce React code, we would get an error. We didn’t use React in our code. So with that in mind we move on;

import React from 'react'
Copy the code

2. Element variable parsing

Let’s create an element variable and place this code on Babel to see the result:

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

Babel compiles to the following form:

The compiled code is as follows:

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

Element Parameter Description:

  • Dom elements
  • attribute
  • The children sub-elements

Use React to parse JSX. If React is not introduced, this code will fail. JSX is actually a syntactic sugar that really needs to be parsed into JS code to execute;

3. Create projects

Let’s create the execution command first:

npm init 
Copy the code

Installation-related dependencies:

npm install --save-dev babel-loader @babel/core
npm install webpack --save-dev
npm install --save-dev @babel/preset-react
npm install --save-dev html-webpack-plugin 
Copy the code

Create project directory:

Webpack configuration:

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    main: './src/index.js'
  },
  devServer: {
    port: 9000,},module: {
    rules: [{test: /\.js$/,
        use: {
          loader: 'babel-loader'.options: {
            presets: ['@babel/preset-env'].plugins: ['@babel/plugin-transform-react-jsx']}}}]},mode: "development".optimization: {
    minimize: false
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'React',})],}Copy the code

Add startup commands:

4. Print the result value

Create a real React project using create-react-app. The installation process is not described in this article. What does the react. createElement actually generate? Print element:

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

const element = <h1 title="foo">Hello</h1>
console.log(element)

const container = document.getElementById("root")
ReactDOM.render(element, container)
Copy the code

Print result:

To simplify, strip out the other attributes (we don’t care about the other attributes) :

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

To recap, react. createElement actually generates an Element object with two properties, type and props. This object has the following properties:

Element object parameters:

  • Type: indicates the label name
  • Props: attribute
    • Title: Indicates the attribute of the label
    • Children: child attribute

5, render simple process

Take a look at render’s simple flow in advance:

Reactdom.render () adds element to a DOM node with id root. We’ll implement this method instead of reactdom.render () in the React source code;

Sample code:

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

1. First, we create a node (element.type) using the element type, in this case h1;

const node = document.createElement(element.type)
Copy the code

2. Set the node property to title.

node["title"] = element.props.title
Copy the code

3. With only one string as a child node, we create a text node and set its nodeValue to element.props. Children;

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

4. Finally, we attach textNode to h1 and h1 to the container;

node.appendChild(text)
container.appendChild(node)
Copy the code

CreateElement implementation (virtual DOM)

Implement React code with our own code;

CreateElement creates an Element object:

const element = {
  type: "h1"./ / label
  props: {
    title: "foo"./ / property
    children: "Hello"./ / the node}},Copy the code

Call method:

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

Based on the call and the return result, design the createElement function as follows:

// react/createElement.js

/** * Create a virtual DOM structure@param {*} The type label *@param {*} Props attribute *@param  {... any} Children themselves@returns Virtual DOM structure */
export function createElement(type, props, ... children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child= >
        typeof child === "object" 
          ? child  
          : createTextElement(child) // It is a text node),}}}/** * Create a text node * when children are non-objects@param {*} Text Indicates the text value *@returns Virtual DOM structure */
function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT".props: {
      nodeValue: text,
      children: [],},}}Copy the code

To visualize this, let’s change the element structure:

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
Copy the code

Test it out:

// src/index.js
import React from '.. /react';

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)

console.log(element
Copy the code

Print result:

7. This section code

Address: gitee.com/linhexs/rea…

3. render

The goal of this section is to implement reactdom.render, which only cares about adding content to the DOM and then dealing with updating and deleting content;

React /react-dom.js;

Once you have your virtual DOM data structure, the next step is to convert it into a real DOM structure and render it on the page. There are basically four steps:

  • Create different types of nodes
  • Adding properties props
  • Iterate over children, recursively calling Render
  • Append the generated node to the root node

Specific implementation steps:

1. Create the react-dom.js file

Create a DOM node and add the new node to the container

// react/react-dom.js
/** * Convert the virtual DOM to the real DOM and add it to the container *@param {element} Virtual DOM *@param {container} Real DOM * /
function render(element, container) {
  const dom = document.createElement(element.type)
  container.appendChild(dom)
}
Copy the code

3. Add each element. Props. Children to the DOM node

 element.props.children.forEach(child= >
    render(child, dom)
  )
Copy the code

4. Handle text nodes

const dom =
    element.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type)
Copy the code

5. Bind attributes to nodes

// Bind 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

6, test,

JSX to DOM library: JSX to DOM

6.1 Introducing the render method into the react/index.js file

6.2 Adding the React. Render method

Add the React. Render method to the SRC /index.js file:

// src/index
import React from '.. /react';

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)

React.render(element, document.getElementById('root'))
Copy the code

6.3 Modifying the WebPack Configuration

Add the index.html file to the SRC directory and add a node with the DOM attribute id:

Modify the WebPack configuration by adding the HTML template:

6.4 run

Run the NPM run start command to start the system, and you can see that the result is displayed successfully:

7,

Use the flow chart to briefly summarize sections 2 and 3:

8. This section code

Code address: gitee.com/linhexs/rea…

Iv. Concurrent Mode & Fiber guidance

1. Problems arise

One problem with the recursive calls in the previous section is that once rendering starts, it cannot be stopped until the entire tree structure is rendered. That is to say, the main thread will be continuously occupied, resulting in the main thread layout, animation and other periodic tasks can not be immediately processed, resulting in visual lag, affecting the user experience.

In the browser, the page is drawn frame by frame, and the refresh frequency of the mainstream browser is 60Hz, that is, the browser refreshes once every (1000ms / 60Hz) 16.6ms. During this frame, the browser needs to complete JS script execution, style layout and style drawing. If the execution time in a certain stage is too long, exceeding 16.6ms, This will block the rendering of the page, resulting in stalling, also known as dropping frames! .

2. How to solve it

Using incremental rendering (splitting the rendering task into chunks and dividing it into multiple frames), we break the work into smaller units, and after each unit is done, if there are other tasks that need to be done, we ask the browser to break the rendering, the oft-heard fiber.

Key points:

  • Incremental rendering
  • Ability to pause, terminate, and reuse rendering tasks when updating
  • Assign priority to different types of updates

3. What is Fiber

Here’s a very classic picture:

To recap: Fiber refers to the tasks that a component will or has done, and each component can have one or more tasks.

4, window. RequestIdleCallback ()

To realize the core of the fiber is window. RequestIdleCallback (), window. RequestIdleCallback () method will be called function in browser free time queue. On the window. RequestIdleCallback (), please click to check the document.

You can think of Requestedlecallback as a setTimeout, but the browser doesn’t tell you when it runs, but instead runs the callback when the main thread is idle.

Window. RequestIdleCallback will in the browser’s idle period called function to wait in line. This enables developers to perform background and low-priority work on the main event loop without affecting the delay of critical events such as animations and input responses.

We use Windows. RequestIdleCallback to implement code:

// Next functional unit
let nextUnitOfWork = null

/** * work loop *@param {*} Deadline Deadline */
function workLoop(deadline) {
  // Stop the loop
  let shouldYield = false

  // The loop condition is that the next unit of work exists and there is no higher priority work
  while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork )// The current frame is about to run out of free time, stop the working loop
    shouldYield = deadline.timeRemaining() < 1
  }
  // You should work in your spare time
  requestIdleCallback(workLoop)
}
// Execute tasks in idle time
requestIdleCallback(workLoop)

// Execute the unit event to return the next unit event
function performUnitOfWork(fiber) {
  // TODO
}

Copy the code

The performUnitOfWork function is implemented in the next section.

5. This section code

Code address: gitee.com/linhexs/rea…

Five, the fiber

1. Fiebr as data structure

Each Fiber node has a React element. How do multiple Fiber nodes connect to form a tree?

For example, we have an attribute structure like this:

react.render(
  <div>
    <h1>
      <p />
      <a />
    </h1>
    <h2 />
  </div>,
  root
)

Copy the code

The React Fiber mechanism relies on the following data structures (linked lists) : each node is a Fiber. A fiber contains the child, sibling, and parent attributes.

// point to the parent Fiber node
this.return = null;
// Point to the sub-fiber node
this.child = null;
// Point to the first sibling Fiber node on the right
this.sibling = null;
Copy the code

2. Rendering process

In rendering, we will create a fiber and set it to Next Tone of work. The rest of the work will be done on the PerforformUnitofWork function, each fiber does three things:

  • Add elements to the DOM
  • Create fiber for the child element of the element
  • Select the next unit of work

The goal is to make it easier to find the next unit of work, using depth-first traversal, finding child nodes first, and finding sibling nodes before.

The above rendering process is described in detail as follows:

  1. When completing fiber work for root, if there are children, fiber is the next unit to work on, and the child of root is div
  2. When you’re done with fiber for div, the next unit of work is H1
  3. The node of h1 is P, moving on to the next unit of work p
  4. P has no children, so let’s go to brother a
  5. A does not have any siblings or children. Return to parent h1
  6. All the children of H1 have done their work. Go to h1’s brother h2
  7. H2 has neither siblings nor children, returning to parent div
  8. Again, div is returning to the parent node root
  9. All rendering work is done at this point

3. Code implementation

1. Extract the DOM node code and place it in createDom();

/** * create DOM *@param {*} Fiber Fiber node */
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
}
Copy the code

2. In the render function, set nextUnitOfWork as the root node of fiber, and the root points only contain a children attribute;

export function render(element, container) {
  // Set the root node as the first unit of work to be done
  nextUnitOfWork = {
    dom: container,
    props: {
      children: [element],
    },
  }
}
Copy the code

3. When the browser has idle time, it processes the root node.

/** * processes the unit of work and returns the next unit event *@param {*} nextUnitOfWork 
 */
function performUnitOfWork(fiber) {
  If fiber does not have a DOM node, create one for it
  if(! fiber.dom) { fiber.dom = createDom(fiber) }// If fiber has a parent node, add fiber.dom to the parent node
  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
}
Copy the code

4. Create a new fiber for each child node;

function performUnitOfWork(fiber) {
  
  // omit the above content
  
  // Get the child node of the current fiber
  const elements = fiber.props.children
  / / index
  let index = 0
  // Last sibling node
  let prevSibling = null

  // Iterate over the child node
  while (index < elements.length) {
    const element = elements[index]
    / / create the fiber
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      dom: null,}}}Copy the code

5. Add the new node to the Fiber tree.

function performUnitOfWork(fiber) {
  
  // omit the above content
  
 while(index < elements.length){
       
     // omit the above content
       
    // Set the first child node as a child of fiber
    if (index === 0) {
      fiber.child = newFiber
    } else if(element) {
      // Children other than the first are set as siblings of the first child
      prevSibling.sibling = newFiber
    }

    prevSibling = newFiber
    index++
 }
}
Copy the code

6. Look for the next unit of work, first for the child, then for the brother, if not, return to the parent node;

function performUnitOfWork(fiber) {
  
  // omit the above content
  
	// Find the next child node, if any returns
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    // If there are sibling nodes, return sibling nodes
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    // Otherwise return the parent node
    nextFiber = nextFiber.parent
  }
}
Copy the code

4, effects,

Modify the SRC /index.js file

Effect:

5. This section code

Code address: gitee.com/linhexs/rea…

Render and submit

1. There are problems

There is also the problem of adding a new node to the DOM every time you process an element, and you will see an incomplete UI due to interruptible operations before rendering the entire tree, which is obviously not acceptable.

2. Processing steps

1. Delete the logic that child nodes are added to parent nodes.

function performUnitOfWork(fiber) {
  // This logic is deleted
  if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
  }
}
Copy the code

2. Add wipRoot to the fiber root node and set it as the next unit of work.

let wipRoot = null
export function render(element, container) {
  // Set the root node as the first unit of work
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
  }
  nextUnitOfWork = wipRoot
}
Copy the code

3. Render the entire fiber as DOM when all the tasks are complete, i.e. there is no next unit of work;

// Submit the task to render the Fiber Tree as a real DOM
function commitRoot(){}function workLoop(deadline) {
  
 	/ / to omit
  
  // There is no next unit of work, submit the current fiber tree
  if(! nextUnitOfWork && wipRoot) { commitRoot() }/ / to omit
  
}
Copy the code

Commit in the commitRoot function, recursively attaching all nodes to the DOM;

/** * Process the submitted Fiber tree *@param {*} fiber 
 * @returns * /
function commitWork(fiber){
  if(! fiber) {return
  }
  const domParent = fiber.parent.dom
  // Add your point to the parent node
  domParent.appendChild(fiber.dom)
  // Render the child node
  commitWork(fiber.child)
  // Render sibling nodes
  commitWork(fiber.sibling)
}

/** * Submit the task to render fiber Tree as real DOM */
function commitRoot(){
  commitWork(wipRoot.child)
  wipRoot = null
}
Copy the code

3. Running results

The results are fine

4. This section code

Code address: gitee.com/linhexs/rea…

Seven, coordination

1, the preface

So far, we’ve only managed to add content to the DOM, so the next goal is to update and delete nodes;

When performing the update, we compare two Fiber trees to update the CHANGED DOM;

For the principles of coordination, go here;

2. Implementation steps

2.1 New Variables

Add the currentRoot variable, save the fiber tree before the root node is updated, add alternate property to every fiber, associate with the old fiber, which is the fiber we submitted to DOM in the last commit phase;

// The root node fiber tree before update
let currentRoot = null

function render (element, container) {
    wipRoot = {
        / / to omit
        alternate: currentRoot
    }
  / / to omit
}

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

2.2 Creating a reconcileChildren and extracting the logic in FormUnitofwork

Extract the code for creating new Fibers into reconcileChildren;

PerformUnitOfWork code changes:

/** * processes the unit of work and returns the next unit event *@param {*} fiber 
 */
function performUnitOfWork(fiber) {
  If fiber does not have a DOM node, create one for it
  if(! fiber.dom) { fiber.dom = createDom(fiber) }// Get the child node of the current fiber
  const elements = fiber.props.children
  
 	/ / coordination
  reconcileChildren(fiber, elements)

  // Find the next child node, if any returns
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    // If there are sibling nodes, return sibling nodes
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    // Otherwise return the parent node
    nextFiber = nextFiber.parent
  }
}
Copy the code

ReconcileChildren code:

/** * coordinate *@param {*} wipFiber 
 * @param {*} elements 
 */
function reconcileChildren(wipFiber,elements){
   / / index
   let index = 0
   // Last sibling node
   let prevSibling = null
 
   // Iterate over the child node
   while (index < elements.length) {
     const element = elements[index]
     / / create the fiber
     const newFiber = {
       type: element.type,
       props: element.props,
       parent: wipFiber,
       dom: null,}// Set the first child node as a child of fiber
     if (index === 0) {
       wipFiber.child = newFiber
     }	else if (element) {
       // Children other than the first are set as siblings of the first child
       prevSibling.sibling = newFiber
     }
 
     prevSibling = newFiber
     index++
   }
}
Copy the code

2.3 Comparison of old and new Fibers

Add the loop condition oldFiber and set newFiber to null;

function reconcileChildren(wipFiber, elements) {
  
  / / to omit
  
  // Last rendered fiber
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child

  / / to omit
  
   while(index < elements.length || oldFiber ! =null) {
     
   		/ / to omit
     
      const newFiber = null
      
      / / to omit
 			
   }
  
  / / to omit
  
}
Copy the code

Compare old and new Fiber to see if any DOM application changes are needed;

function reconcileChildren(wipFiber, elements) {
  
  / / to omit
  
  // Last rendered fiber
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child

  / / to omit
  
   while(index < elements.length || oldFiber ! =null) {
     
   		/ / to omit
     	
      // Type judgment
    	const sameType = oldFiber && element && element.type == oldFiber.type
			
      // The same type needs to be updated
    	if (sameType) {
    	  // TODO update the node
   	 	}
     	
     	// New types exist and are different from old ones
    	if(element && ! sameType) {// TODO add this node
   	 	}
     
     	// The old exists and the type is different from the new and needs to be removed
    	if(oldFiber && ! sameType) {// TODO delete the oldFiber's node
   	 	}
     	
     	// Handle older fiber siblings
     	if (oldFiber) {
      	oldFiber = oldFiber.sibling
    	}
      
     / / to omit
 			
   }
  
  / / to omit
  
}



Copy the code

When the type is the same, create a new Fiber, retain the dom node of the old fiber, update props, and add an effectTag attribute to identify the current execution state.

function reconcileChildren(wipFiber, elements) { 
	while(index < elements.length || oldFiber ! =null) {
    
    / / to omit
    
		// Update props only for the same type
    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE",}}/ / to omit
}
Copy the code

In cases where the meta requires a new DOM node, we mark the new fiber with the PLACEMENT Effect tag;

function reconcileChildren(wipFiber, elements) { 
	while(index < elements.length || oldFiber ! =null) {
    
    / / to omit
    
    // New types exist and are different from old ones
    if(element && ! sameType) { newFiber = {type: element.type,
        props: element.props,
        dom: null.parent: wipFiber,
        alternate: null.effectTag: "PLACEMENT",}}/ / to omit
}
Copy the code

For the case that nodes need to be deleted, there is no new fiber, add the Effect label to the old fiber and delete the old fiber;

function reconcileChildren(wipFiber, elements) { 
	while(index < elements.length || oldFiber ! =null) {
    
    / / to omit
    
   // The old exists and the type is different from the new and needs to be removed
   if(oldFiber && ! sameType) { oldFiber.effectTag ="DELETION"
     deletions.push(oldFiber)
   }
    
    / / to omit
}
Copy the code

Set up an array to store the nodes that need to be removed.

let deletions = null

function render(element, container) {
  
  / / to omit
  
  deletions = []
  
  / / to omit
}
Copy the code

When rendering the DOM, traversal removes old nodes;

function commitRoot() {
  deletions.forEach(commitWork)
 
  / / to omit
  
}
Copy the code

Modify commitWork to handle effectTag and new PLACEMENT.

function commitWork(fiber) {
  
 	/ / to omit
  
  if (
    fiber.effectTag === "PLACEMENT"&& fiber.dom ! =null
  ) {
    domParent.appendChild(fiber.dom)
  }
  
  / / to omit
  
}
Copy the code

Process deleted node tags;

function commitWork(fiber) {
  
 	/ / to omit
  
  // Handle deleting node flags
  else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom)
  }
  
  / / to omit
  
}


Copy the code

To process the update node, add the updateDom method and update the props property;

function updateDom(){}function commitWork(fiber) {
  
 	/ / to omit
  
  // Handle deleting node flags
  else if (
    fiber.effectTag === "UPDATE"&& fiber.dom ! =null
  ) {
    updateDom(
      fiber.dom,
      fiber.alternate.props,
      fiber.props
    )
  }
  
  / / to omit
  
}


Copy the code

The updateDom method updates the props based on the update type.

const isProperty = key= >key ! = ="children" 
// Whether there is a new attribute
const isNew = (prev, next) = > key= >prev[key] ! == next[key]// Whether it is an old attribute
const isGone = (prev, next) = > key= >! (keyin next)

/** * Update the DOM attribute *@param {*} dom 
 * @param {*} PrevProps old properties *@param {*} NextProps New property */
function updateDom(dom, prevProps, nextProps) {
  // Remove the old attributes
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach(name= > {
      dom[name] = ""
    })

  // Set new properties
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name= > {
      dom[name] = nextProps[name]
    })
}
Copy the code

Modify the createDom method and change the update attribute logic to the updateDom method call;

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

  updateDom(dom, {}, fiber.props)
  return dom
}
Copy the code

Add event listener, starting with on, and modify isProperty method;

const isEvent = key= > key.startsWith("on")
const isProperty = key= >key ! = ="children" && !isEvent(key)
Copy the code

Modify the updateDom method to handle event listening and remove it from the node;

function updateDom(dom, prevProps, nextProps) {
  // Remove the old event 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]
      )
    })
  
  / / to omit
  
}
Copy the code

Add a new event listener

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

3, achieve the effect

Modify the SRC /index.js code:

// src/index
import React from '.. /react';

const container = document.getElementById("root")

const updateValue = e= > {
    rerender(e.target.value)
}

const rerender = value= > {
    const element = (
        <div>
            <input onInput={updateValue} value={value} />
            <h2>Hello {value}</h2>
        </div>
    )
    React.render(element, container)
}

rerender("World")


Copy the code

Run:

4. This section code

Code address: gitee.com/linhexs/rea…

Function component

1, the preface

Let’s start by writing a function component:

// src/index
import React from '.. /react'

function App(props){
    return <h1>H1,{props.name}!</h1>
}

const element = (<App name='foo'></App>)

React.render(element, document.getElementById("root"))



Copy the code

There are two differences between function components and tag components:

  • Fiber in the function component has no DOM node
  • Children are obtained by running functions instead of props

2. Function realization

2.1 performUnitOfWork adds the judgment function component;

function performUnitOfWork(fiber) {
  // check whether it is a function
  const isFunctionComponent =
    fiber.type instanceof Function
  if (isFunctionComponent) {
    updateFunctionComponent(fiber)
  } else {
    // Update common nodes
    updateHostComponent(fiber)
  }
  
  / / to omit
  
}  


function updateFunctionComponent(fiber) {
  // TODO
}

function updateHostComponent(fiber) {
  // TODO
}

Copy the code

2.2 Remove the reconcileChildren method from The performUnitOfWork into the updateHostComponent function;

function updateHostComponent(fiber) {
  if(! fiber.dom) { fiber.dom = createDom(fiber) } reconcileChildren(fiber, fiber.props.children) }Copy the code

2.3 Handle function component, execute function component to get children;

/** * function components handle *@param {*} fiber 
 */
function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}
Copy the code

2.4 Next let’s deal with fiber without DOM;

function commitWork (fiber) {
  
    / / to omit
  
    let domParentFiber = fiber.parent
    // Keep looking up until you find a node with a DOM
    while(! domParentFiber.dom) { domParentFiber = domParentFiber.parent }const domParent = domParentFiber.dom
    
    / / to omit
    
}
Copy the code

2.5 When removing a node, you need to keep looking down until you find a child node with a DOM node.

/** * Process the submitted Fiber tree *@param {*} fiber 
 * @returns * /
function commitWork(fiber) {
  
  / / to omit
  
  // Handle deleting node flags
  } else if (fiber.effectTag === "DELETION") {
    commitDeletion(fiber, domParent)
  } 
  
  / / to omit
}



/** * In the delete case, keep looking down until you find the child node * with the DOM@param {*} fiber 
 * @param {*} domParent 
 */
function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom)
  } else {
    commitDeletion(fiber.child, domParent)
  }
}
Copy the code

3, achieve the effect

4. This section code

Code address: gitee.com/linhexs/rea…

Nine, hooks

1, the preface

In this section, we will add the hooks that are the core function of React16.8.

Write a counter function;

// src/index
import React from '.. /react'

function Counter() {
    const [state, setState] = React.useState(1)
    return (
        <div>
            <h1 >
                Count: {state}
            </h1>
            <button onClick={()= > setState(c => c + 1)}>+1</button>
        </div>)}const element = <Counter />

React.render(element, document.getElementById("root"))
Copy the code

2. Implementation steps

2.1 Adding the useState function

// react/react-dom.js

function useState(initial){
  // todo
}
Copy the code

2.2 Initializing global Variables

We know that the internal state is lost every time a function is called again, so we need a variable that records the internal state;

Set working fiber and add an array to record multiple calls to useState, using indexes to track;

let wipFiber = null
let hookIndex = null

/** * function components handle *@param {*} fiber 
 */
function updateFunctionComponent(fiber) {
  wipFiber = fiber
  hookIndex = 0
  wipFiber.hooks = []
  
	/ / to omit
  
}
Copy the code

2.3 Adding the state of the useState function

/ * * *@param {*} Initial The initial value passed in *@returns * /
function useState(initial) {
  // Check for old hooks
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex]
  // If there is an old one, copy it to the new one, if not initialized
  const hook = {
    state: oldHook ? oldHook.state : initial,
  }

  wipFiber.hooks.push(hook)
  hookIndex++
  return [hook.state]
}
Copy the code

2.4 Adding setState of the useState function

Set up a new in-progress work root as the next unit of work so that the work cycle can start a new render phase;

// Set the hooks state
  const setState = action= > {
    debugger
    hook.queue.push(action)
    // Set a new in-progress work root as the next unit of work so that the work cycle can start a new render phase
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot,
    }
    nextUnitOfWork = wipRoot
    deletions = []
  }
Copy the code

2.5 Updating status

function useState(initial) { 
  
	/ / to omit
  
	const actions = oldHook ? oldHook.queue : []
	actions.forEach(action= > {
    hook.state = typeof action === 'function' ? action(hook.state) : action
 	})
  
 / / to omit
  
}
Copy the code

3. Running results

Export the result of the useState function;

Running results:

4. This section code

Code address: gitee.com/linhexs/rea…

The Class component

1, the preface

In this section we add a class component to improve react;

// src/index
import React from '.. /react'

class Counter extends React.Component {
    render() {
        return (
            <div>
                <h1>I am a</h1>
                <h2>The class components</h2>
            </div>)}}const element = <Counter />

React.render(element, document.getElementById("root"))
Copy the code

2. Implementation steps

2.1 Component. Js

When we write a class Component, we inherit a react.component. js file, so we use isReactComponent to identify the class Component.

// react/Component
export function Component(props) {
  this.props = props;
}
Component.prototype.isReactComponent = true;
Copy the code

2.2 Modifying the performUnitOfWork method

Modify the performUnitOfWork method to add class component and function component judgment;

function performUnitOfWork(fiber) {
  // function component class component handling
  if (fiber.type && typeof fiber.type === 'function') {
    fiber.type.prototype.isReactComponent ? updateClassComponent(fiber) : updateFunctionComponent(fiber)
  } else {
    // Update common nodes
    updateHostComponent(fiber)
  }
  
  / / to omit
  
}

/** * class components handle *@param {*} fiber 
 */
updateClassComponent(fiber){
  // todo
}
Copy the code

2.3 updateClassComponent method

/** * class components handle *@param {*} fiber 
 */
function updateClassComponent(fiber){
  const {type, props} = fiber;
  const children = [new type(props).render()];
  reconcileChildren(fiber, children)
}
Copy the code

3, achieve the effect

4. This section code

Code address: gitee.com/linhexs/rea…

Xi. Reference Links:

  1. Pomb. Us/build – your -…

  2. react.iamkasong.com/