This is the seventh day of my participation in the More text Challenge. For details, see more text Challenge

Why platinum? Because there is still a long way to go. This article only implements the simple version of React, referring to the basic functions of React 16.8, including virtual DOM, Fiber, Diff algorithm, functional components, hooks, etc.

One, foreword

This article is based on pomb.us/build-your-… Implement a simple version of React.

B station -React source code, you are in the first layer.

The simulated version is React 16.8.

The following functions will be implemented:

  1. CreateElement (virtual DOM);
  2. Render.
  3. Concurrent mode;
  4. Fibers;
  5. Render and Commit Phases;
  6. Coordination (Diff algorithm);
  7. Function component;
  8. Hooks;

Now for dinner, read on.

2, preparation,

1. React Demo

Let’s start with a simple React Demo that looks like this:

const element = <div title="foo">hello</div>
const container = document.getElementById('container')
ReactDOM.render(element, container);
Copy the code

See reactDemo for the complete source code for this example

Open reactdemo.html in your browser and it looks like this:

We need to implement our React, so we need to know what the code above does.

1.1 element

Const Element =

123

is actually a JSX syntax.

React explains the JSX as follows:

JSX is a JavaScript syntax extension. It is similar to a template language, but it has the full power of JavaScript. JSX will eventually be compiled by Babel as a function call called react.createElement ().

Compile const Element =

123

online with Babel.

Const element =

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

Let’s see what the React. CreateElement object actually looks like.

Try printing in demo:

const element = <div title="foo">hello</div>
console.log(element)
const container = document.getElementById('container')
ReactDOM.render(element, container);
Copy the code

You can see the output element as follows:

To simplify element:

const element = {
    type: 'div'.props: {
        title: 'foo'.children: 'hello'}}Copy the code

To summarize, React. CreateElement actually generates an Element object with the following properties:

  • Type: the tag name
  • props
    • Title: indicates the tag attribute
    • Children: the child nodes

1.2 render

Reactdom.render () adds the element to the DOM node with the ID container. Next we’ll simply write a method instead of reactdom.render ().

  1. Create a node with the tag element.
const node = document.createElement(element.type)
Copy the code
  1. Set the title of the node to element.props.
node["title"] = element.props.title
Copy the code
  1. Create an empty text node text;
const text = document.createTextNode("")
Copy the code
  1. Set the nodeValue of the text node to element.props. Children;
text["nodeValue"] = element.props.children
Copy the code
  1. Add text node to node;
node.appendChild(text)
Copy the code
  1. Add node to Container
container.appendChild(node)
Copy the code

See reactDemo2 for the complete source code for this example

Run the source code, and the result is the same as when introducing React:

Three,

Above, we simply replaced React. CreateElement and Reactdom. render methods by simulating React, and then we will really start to implement various functions of React.

1. CreateElement (virtual DOM)

CreateElement creates an Element object with the following structure:

// Virtual DOM structure
const element = {
    type: 'div'./ / tag name
    props: { // Node attributes, including children
        title: 'foo'./ / the title attribute
        children: 'hello' // Child nodes, note: this should actually be an array structure to help us store more child nodes}}Copy the code

Based on the structure of the element, the createElement function is designed. The code is as follows:

/** * Create virtual DOM structure *@param {type} Tag name *@param {props} Property object@param {children} Child node *@return {element} Virtual DOM * /
function createElement (type, props, ... children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child= > 
                typeof child === 'object'
                ? child
                : createTextElement(child)
            )
        }
    }
}
Copy the code

When children are non-objects, we should create a textElement element as follows:

/** * Create text node *@param {text} The text value *@return {element} Virtual DOM * /
function createTextElement (text) {
    return {
        type: "TEXT_ELEMENT".props: {
            nodeValue: text,
            children: []}}}Copy the code

Let’s try this with the following code:

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

See reactDemo3 for the complete source code for this example

The resulting Element object looks like this:

const element = {
    "type": "div"."props": {
        "id": "foo"."children": [{"type": "a"."props": {
                    "children": [{"type": "TEXT_ELEMENT"."props": {
                                "nodeValue": "bar"."children": []}}]}}, {"type": "b"."props": {
                    "children"[]}}]}}Copy the code

JSX

In fact, when we develop with React, we don’t create components like this:

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

Instead, through JSX syntax, the code looks like this:

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

In myReact, we can use JSX syntax by adding comments that tell Babel to translate the function we specify, as follows:

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

See reactDemo4 for the complete source code for this example

2. render

The render function helps us add the Element to the real node.

It will be achieved in the following steps:

  1. Create a DOM node of type Element. type and add it to the container.
/** * Add virtual DOM to real DOM *@param {element} Virtual DOM *@param {container} Real DOM * /
function render (element, container) {
    const dom = document.createElement(element.type)
    container.appendChild(dom)
}
Copy the code
  1. Add both element.children to the DOM node;
element.props.children.forEach(child= > 
    render(child, dom)
)
Copy the code
  1. Special processing of text nodes;
const dom = element.type === 'TEXT_ELEMENT'
    ? document.createTextNode("")
    : document.createElement(element.type)
Copy the code
  1. Add the element’s props attribute to the DOM;
const isProperty = key= >key ! = ="children"
Object.keys(element.props)
    .filter(isProperty)
    .forEach(name= > {
      dom[name] = element.props[name]
})
Copy the code

We have implemented the function of rendering JSX to the real DOM. Let’s try it out. The code is as follows:

const myReact = {
    createElement,
    render
}
/ * *@jsx myReact.createElement */
const element = (
    <div id='foo'>
        <a>bar</a>
        <b></b>
    </div>
)

myReact.render(element, document.getElementById('container'))
Copy the code

See reactDemo5 for the complete source code for this example

The result is shown in the figure, and the output is successful:

3. Concurrent mode (requestIdleCallback)

Let’s see what happens to the child nodes in the render method:

/** * Add virtual DOM to real DOM *@param {element} Virtual DOM *@param {container} Real DOM * /
function render (element, container) {
    / / to omit
    // Iterate over all the child nodes and render them
    element.props.children.forEach(child= >
        render(child, dom)
    )
    / / to omit
}
Copy the code

The problem with this recursive call is that once the render starts, all nodes and their children will be rendered before the process ends.

When the DOM tree is large, the page is stuck during the rendering process, and interactive operations such as user input cannot be carried out.

The following steps can be taken to solve the above problems:

  1. Allow to interrupt the rendering work, if there is a higher priority work inserted, then temporarily interrupt the browser rendering, until the completion of the work, resume the browser rendering;
  2. Break down the rendering work into small units;

Use requestIdleCallback to solve the problem of allowing interrupts to render work.

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.

Can view the document: document window. RequestIdleCallback detail

The code is as follows:

// Next unit of work
let nextUnitOfWork = null
/** * workLoop function *@param {deadline} Deadline */
function workLoop(deadline) {
  // Whether to stop working loop functions
  let shouldYield = false
  
  // If there is a next unit of work and there is no other work of higher priority, the loop executes
  while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork )// If the deadline is approaching, stop the working loop function
    shouldYield = deadline.timeRemaining() < 1
  }
  
  // Notify the browser that the workLoop should be executed during idle time
  requestIdleCallback(workLoop)
}
// Notify the browser that the workLoop should be executed during idle time
requestIdleCallback(workLoop)

// Execute the unit event and return the next unit event
function performUnitOfWork(nextUnitOfWork) {
  // TODO
}
Copy the code

PerformUnitOfWork is used to execute unit events and return the next unit event, as described below.

4. fiber

RequestIdleCallback allows the browser to render units of work at idle time, avoiding the problem of overrendering and page stalling.

Note: requestIdleCallback is actually unstable and not recommended for use in production environments. This example is only used to simulate React ideas. React itself does not use requestIdleCallback to render units of work in the browser at idle time.

On the other hand, In order to make the render work separate into small units, React designed Fiber.

Each element is a fiber structure, and each fiber is a rendering unit.

So fiber is both a data structure and a unit of work.

Fiber is described below with a simple example.

Suppose you want to render such an Element tree:

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

The generated fiber tree is shown in figure:

Orange is the child node, yellow is the parent node, and blue is the sibling node.

Each fiber has a link to its first child, its next sibling, and its parent. This data structure makes it easier to find the next unit of work.

The arrow in the image above also shows the rendering process of Fiber, which is described in detail below:

  1. Starting from root, find the first child div;
  2. Find the first child of the div h1;
  3. Find h1’s first child p;
  4. Find the first child node of P, if there is no child node, then find the next sibling node, find the sibling node a of P;
  5. Find the first child node of A, if there is no child node, and no sibling node, then find the next sibling node of its parent node, find the sibling node h2 of a’s parent node;
  6. Look for the first child of h2, can’t find it, look for the sibling, can’t find it, look for the sibling of the parent div, can’t find it, continue to look for the sibling of the parent div, find root;
  7. Step 6: The root node has been found, rendering is complete.

The following rendering process is implemented in code.

  1. Detach the part of Render that creates the DOM node into the creactDOM function;
/** * createDom Creates a DOM node@param {fiber} Fiber node *@return {dom} Dom node * /
function createDom (fiber) {
    // If it is a text type, create an empty text node. If it is not a text type, create a node of type type
    const dom = fiber.type === 'TEXT_ELEMENT'
        ? document.createTextNode("")
        : document.createElement(fiber.type)

    // isProperty indicates an attribute that is not children
    const isProperty = key= >key ! = ="children"
    
    // Iterates over props to add properties to the DOM
    Object.keys(fiber.props)
        .filter(isProperty)
        .forEach(name= > {
            dom[name] = fiber.props[name]
        })
        
    / / returns the dom
    return dom
}
Copy the code
  1. Set the first unit of work in Render as the fiber root node;

The fiber root node only contains the children attribute, and the value is the parameter fiber.

// Next unit of work
let nextUnitOfWork = null
/** * Add fiber to real DOM *@param {element} fiber
 * @param {container} Real DOM * /
function render (element, container) {
    nextUnitOfWork = {
        dom: container,
        props: {
            children: [element]
        }
    }
}
Copy the code
  1. RequestIdleCallback renders the fiber when the browser is idle;
/** * workLoop function *@param {deadline} Deadline */
function workLoop(deadline) {
  // Whether to stop working loop functions
  let shouldYield = false
  
  // If there is a next unit of work and there is no other work of higher priority, the loop executes
  while(nextUnitOfWork && ! shouldYield) { nextUnitOfWork = performUnitOfWork( nextUnitOfWork )// If the deadline is approaching, stop the working loop function
    shouldYield = deadline.timeRemaining() < 1
  }
  
  // Notify the browser that the workLoop should be executed during idle time
  requestIdleCallback(workLoop)
}
// Notify the browser that the workLoop should be executed during idle time
requestIdleCallback(workLoop)
Copy the code
  1. Render fiber function performUnitOfWork;
/** * performUnitOfWork *@param {fiber} fiber
 * @return {nextUnitOfWork} Next unit of work */
function performUnitOfWork(fiber) {
  // TODO adds a DOM node
  // TODO create a filber
  // TODO returns the next unit of work (fiber)
}
Copy the code

4.1 Adding a DOM Node

function performUnitOfWork(fiber) {
    // Create a DOM node for fiber if it does not have one
    if(! fiber.dom) { fiber.dom = createDom(fiber) }// If fiber has a parent, add fiber.dom to the parent
    if (fiber.parent) {
        fiber.parent.dom.appendChild(fiber.dom)
    }
}
Copy the code

4.2 new filber

function performUnitOfWork(fiber) {
    // ~ ~ omit ~ ~
    / / child nodes
    const elements = fiber.props.children
    / / index
    let index = 0
    // Last sibling node
    let prevSibling = null
    // Iterate over the child nodes
    while (index < elements.length) {
        const element = elements[index]

        / / create the fiber
        const newFiber = {
            type: element.type,
            props: element.props,
            parent: fiber,
            dom: null,}// Set the first child node to be a child of fiber
        if (index === 0) {
            fiber.child = newFiber
        } else if (element) {
        // The first child is set as the sibling of that node
            prevSibling.sibling = newFiber
        }

        prevSibling = newFiber
        index++
    }
}
Copy the code

4.3 Return to the next unit of Work (fiber)


function performUnitOfWork(fiber) {
    // ~ ~ omit ~ ~
    // If there are children, return them
    if (fiber.child) {
        return fiber.child
    }
    let nextFiber = fiber
    while (nextFiber) {
        // If there is a sibling, return the sibling
        if (nextFiber.sibling) {
            return nextFiber.sibling
        }

        // Otherwise, continue through the while loop until root is found.
        nextFiber = nextFiber.parent
    }
}
Copy the code

We have implemented the ability to render fiber to the page, and the rendering process is interruptible.

Now try it out. The code looks like this:

const element = (
    <div>
        <h1>
        <p />
        <a />
        </h1>
        <h2 />
    </div>
)

myReact.render(element, document.getElementById('container'))
Copy the code

See reactDemo7 for the complete source code for this example

Output dom as expected, as shown in the figure below:

5. Render commit phase

Since we made the rendering process interruptible, we certainly didn’t want the browser to show the user a half-rendered UI.

The optimization of the render commit phase is as follows:

  1. Remove the logic in performUnitOfWork about adding children to parent nodes;
function performUnitOfWork(fiber) {
    // Delete this paragraph
    if (fiber.parent) {
       fiber.parent.dom.appendChild(fiber.dom)
    }
}
Copy the code
  1. Add a root variable to store the fiber root node.
/ / the root node
let wipRoot = null
function render (element, container) {
    wipRoot = {
        dom: container,
        props: {
            children: [element]
        }
    }
    // The next unit of work is the root node
    nextUnitOfWork = wipRoot
}
Copy the code
  1. When all fibers work, nextUnitOfWork is undefined and the real DOM is rendered.
function workLoop (deadline) {
    / / to omit
    if(! nextUnitOfWork && wipRoot) { commitRoot() }/ / to omit
}
Copy the code
  1. New commitRoot function (commitRoot) to render the fiber tree to a real DOM recursion.
// Render the fiber tree as a real DOM;
function commitRoot () {
    commitWork(wipRoot.child)
    // This must be set to null, otherwise the workLoop will continue to execute when the browser is idle.
    wipRoot = null
}
/** * performUnitOfWork *@param {fiber} fiber* /
function commitWork (fiber) {
    if(! fiber)return
    const domParent = fiber.parent.dom
    domParent.appendChild(fiber.dom)
    // Render the child node
    commitWork(fiber.child)
    // Render the sibling node
    commitWork(fiber.sibling)
}
Copy the code

See reactDemo8 for the complete source code for this example

The running results of the source code are as follows:

6. Coordination (Diff algorithm)

When element is updated, the fiber tree before update needs to be compared with the fiber tree after update. After the comparison result is obtained, only the DOM node corresponding to the changed fiber is updated.

Through coordination, the number of operations on the real DOM is reduced.

1. currentRoot

Add currentRoot variable to save the alternate tree before the root node is updated. Add alternate attribute for fiber to save the alternate tree before the root node is updated.

let currentRoot = null
function render (element, container) {
    wipRoot = {
        / / to omit
        alternate: currentRoot
    }
}
function commitRoot () {
    commitWork(wipRoot.child)
    currentRoot = wipRoot
    wipRoot = null
}
Copy the code

2. performUnitOfWork

The new fiber logic in performUnitOfWork is extracted to reconcileChildren function.

/** * Coordinate child node *@param {fiber} fiber
 * @param {elements} */ is a child node of fiber
function reconcileChildren (fiber, elements) {
    // Used to count the index value of the child node
    let index = 0
    // Last sibling node
    let prevSibling = null

    // Iterate over the child nodes
    while (index < elements.length) {
        const element = elements[index]

        / / the new fiber
        const newFiber = {
            type: element.type,
            props: element.props,
            parent: fiber,
            dom: null,}// The first child node of fiber is its child node
        if (index === 0) {
            fiber.child = newFiber
        } else if (element) {
        // The other children of fiber are siblings of its first child
            prevSibling.sibling = newFiber
        }

        // Assign the new newFiber to prevSibling, so you can easily add sibling nodes to newFiber
        prevSibling = newFiber
        
        // Index value + 1
        index++
    }
}
Copy the code

3. reconcileChildren

Comparing old and new fibers in reconcileChildren;

3.1 When old and new fiber types are the same

Keep the DOM, UPDATE props only, and set the effectTag to UPDATE;

function reconcileChildren (wipFiber, elements) {
    // ~ ~ omit ~ ~
    // oldFiber can be found in wipfiber.alternate
    let oldFiber = wipFiber.alternate && wipFiber.alternate.child

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

        // If the fiber type is the same
        const sameType =
            oldFiber &&
            element &&
            element.type == oldFiber.type

        // Update props only if the types are the same
        if (sameType) {
            newFiber = {
                type: oldFiber.type,
                props: element.props,
                dom: oldFiber.dom,
                parent: wipFiber,
                alternate: oldFiber,
                effectTag: "UPDATE",}}// ~ ~ omit ~ ~
    }
    // ~ ~ omit ~ ~
}
Copy the code

3.2 When old and new fiber types are different and there are new elements

Create a new DOM node and set the effectTag to PLACEMENT;

function reconcileChildren (wipFiber, elements) {
    // ~ ~ omit ~ ~
    if(element && ! sameType) { newFiber = {type: element.type,
            props: element.props,
            dom: null.parent: wipFiber,
            alternate: null.effectTag: "PLACEMENT",}}// ~ ~ omit ~ ~
}
Copy the code

3.3 When the types of old and new fibers are different and there are old fibers

Delete the old fiber and set the effectTag to DELETION.

function reconcileChildren (wipFiber, elements) {
    // ~ ~ omit ~ ~
    if(oldFiber && ! sameType) { oldFiber.effectTag ="DELETION"
        deletions.push(oldFiber)
    }
    // ~ ~ omit ~ ~
}
Copy the code

4. deletions

Create a deletions array to store the fiber node to be deleted. When rendering DOM, traverse the deletions to delete the old fiber node.

let deletions = null
function render (element, container) {
    / / to omit
    // Initialize the deletions array with render
    deletions = []
}

// When rendering the DOM, traverse deletions to delete the old fiber
function commitRoot () {
    deletions.forEach(commitWork)
}
Copy the code

5. commitWork

The effectTag of fiber is determined in commitWork and processed separately.

5.1 PLACEMENT

When the effectTag of fiber is PLACEMENT, it means that the fiber is added and the node is added to the parent node.

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

5.2 DELETION

When the effectTag of fiber is PLACEMENT, it means that the fiber is deleted and the node of the parent node is deleted.

else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom)
}
Copy the code

5.3 DELETION

When the effectTag of fiber is set to UPDATE, it means to UPDATE fiber and UPDATE the props property.

else if (fiber.effectTag === 'UPDATE'&& fiber.dom ! =null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props)
}
Copy the code

The updateDom function updates the props property based on the update type.

const isProperty = key= >key ! = ="children"

// Whether the attribute is new
const isNew = (prev, next) = > key= >prev[key] ! == next[key]// Is the old attribute
const isGone = (prev, next) = > key= >! (keyin next)

function updateDom(dom, prevProps, nextProps) {
    // Delete old attributes
    Object.keys(prevProps)
        .filter(isProperty)
        .filter(isGone(prevProps, nextProps))
        .forEach(name= > {
            dom[name] = ""
        })

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

In addition, add update and delete event properties to updateDom to make it easier to track updates to fiber events.

function updateDom(dom, prevProps, nextProps) {
    // ~ ~ omit ~ ~
    const isEvent = key= > key.startsWith("on")
    // Delete old or changed events
    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]
          )
        })

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

Replace the logic for setting props in creactDOM.

function createDom (fiber) {
    const dom = fiber.type === 'TEXT_ELEMENT'
        ? document.createTextNode("")
        : document.createElement(fiber.type)
    // Look at the duck
    updateDom(dom, {}, fiber.props)
    return dom
}
Copy the code

Create a new example with input form items and try updating Element with the following code:

/ * *@jsx myReact.createElement */
const container = document.getElementById("container")

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

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

rerender("World")
Copy the code

See reactDemo9 for the complete source code for this example

The output results are as follows:

7. Functional components

Let’s start with a simple example of a functional component:

MyReact does not yet support functional components. The following code will report an error when running, but this is only used as a comparison to how functional components are normally used.

/ * *@jsx myReact.createElement */
const container = document.getElementById("container")

function App (props) {
    return (
        <h1>hi~ {props.name}</h1>)}const element = (
    <App name='foo' />
)

myReact.render(element, container)
Copy the code

Functional components differ from HTML tag components in two ways:

  • The fiber function component has no DOM node;
  • The children of the function component needs to be obtained after running the function;

The function component is implemented by the following steps:

  1. Modify performUnitOfWork to execute a fiber unit of work based on the fiber type.
function performUnitOfWork(fiber) {
    // Whether it is a function component
    const isFunctionComponent = fiber && fiber.type && fiber.type instanceof Function
    // If it is a function component, execute updateFunctionComponent
    if (isFunctionComponent) {
        updateFunctionComponent(fiber)
    } else {
    // If it is not a function component, execute updateHostComponent
        updateHostComponent(fiber)
    }
    / / to omit
}
Copy the code
  1. Define the updateHostComponent function to execute non-functional components;

Nonfunctional components can pass fiber.props. Children directly as a parameter.

function updateHostComponent(fiber) {
    if(! fiber.dom) { fiber.dom = createDom(fiber) } reconcileChildren(fiber, fiber.props.children) }Copy the code
  1. Define updateFunctionComponent and execute the function component.

The function component needs to run to get fiber.children.

function updateFunctionComponent(fiber) {
    // fiber.type is the function component itself, and fiber.props is the function component parameter
    const children = [fiber.type(fiber.props)]
    reconcileChildren(fiber, children)
}
Copy the code
  1. Modify the commitWork function to accommodate fibers without DOM nodes.

4.1 Modify the domParent acquisition logic to keep searching upward through the while loop until the parent fiber with DOM nodes is found;

function commitWork (fiber) {
    / / to omit
    let domParentFiber = fiber.parent
    // If fiber.parent does not have a DOM node, continue to look for fiber.parent-parent-dom until there is a DOM node.
    while(! domParentFiber.dom) { domParentFiber = domParentFiber.parent }const domParent = domParentFiber.dom
    / / to omit
}
Copy the code

4.2 Modify the logic of node deletion. When deleting a node, it is necessary to keep looking down until the sub-fiber with DOM node is found;

function commitWork (fiber) {
    / / to omit
    // If the update type of fiber is delete, run the commitDeletion command
     else if (fiber.effectTag === "DELETION") {
        commitDeletion(fiber.dom, domParent)
    }
    / / to omit
}

// Delete the node
function commitDeletion (fiber, domParent) {
    // If the fiber has dom nodes, delete them directly
    if (fiber.dom) {
        domParent.removeChild(fiber.dom)
    } else {
    // If there is no DOM node in this fiber, continue to look for its children and delete them
        commitDeletion(fiber.child, domParent)
    }
}
Copy the code

Let’s take a look at the above example. The code is as follows:

/ * *@jsx myReact.createElement */
const container = document.getElementById("container")

function App (props) {
    return (
        <h1>hi~ {props.name}</h1>)}const element = (
    <App name='foo' />
)

myReact.render(element, container)
Copy the code

See reactDemo10 for the complete source code for this example

The running results are as follows:

8. hooks

Let’s continue to add the ability to manage state to myReact. The expectation is that function components have their own state and can retrieve and update state.

A function component with a count function is as follows:

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

It is known that a useState method is required to retrieve and update the status.

Again, the rendering of the function component is based on the execution of the function component, so if the above Counter wants to update the count, it will execute the Counter function once for each update.

Through the following steps:

  1. New global variable wipFiber;
// The current unit of work is fiber
let wipFiber = null
function updateFunctionComponent(fiber) {
    wipFiber = fiber
    // Hook of current unit of work fiber
    wipFiber.hook = []
    / / to omit
}
Copy the code
  1. New useState function;
// initial indicates the initial parameter, in this case initail=1
function useState (initial) {
    // Whether there is an old hook, which stores the last updated hook
    const oldHook =
        wipFiber.alternate &&
        wipFiber.alternate.hook

    // Initialize the hook. The hook state is either the old hook state or the original hook state
    const hook = {
        state: oldHook ? oldHook.state : initial,
        queue: [],}// Get all the actions from the old hook queue and apply them to the new hook state
    const actions = oldHook ? oldHook.queue : []
    actions.forEach(action= > {
        hook.state = action(hook.state)
    })

    // Set the hook state
    const setState = action= > {
        // Add the action to the hook queue
        hook.queue.push(action)
        // Update the render
        wipRoot = {
            dom: currentRoot.dom,
            props: currentRoot.props,
            alternate: currentRoot,
        }
        nextUnitOfWork = wipRoot
        deletions = []
    }

    // Add the hook to the unit of work
    wipFiber.hook = hook
    
    // Returns the hook state and the function that sets the hook
    return [hook.state, setState]
}
Copy the code

Let’s run the count component as follows:

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

See reactDemo11 for the complete source code for this example

The running results are as follows:

This section simply implements the myReact hooks functionality.

After all, react has many implementations worth studying and studying. I hope there will be more functions of React in the next installment.

conclusion

This paper refers to pomB.us for learning, and implements the custom React including virtual DOM, Fiber, Diff algorithm, functional components, hooks and other functions.

In the process of implementation, the editor has a general grasp of the basic terms and implementation ideas of React. Pomb. us is very suitable for beginners to learn materials, you can directly learn through pomB. us, but also recommended to follow this article step by step to realize the common functions of React.

Source code: Github source code.

Suggest following step by step knock, carry on practical practice.

I hope it can help you, thanks for reading ~

Don’t forget to encourage me with a thumbs-up, pen refill ❤️

The resources

  • Pomb. Us/build – your -…

  • Casson – B station -React source code, what floor are you on

  • Write a simple React by hand