This is the 25th day of my participation in the Gwen Challenge.More article challenges

Project code, we can continue to follow up learning, mutual encouragement!!

preface

Hello everyone, I am Komura. In the previous article, we have completed the construction of the Fiber object of the virtualDOM object. Next, we will complete the second phase of the Fiber algorithm

  • Building an Array of Effects
  • Implement initial rendering
  • Processing class component
  • Processing function component
  • Implement update node
  • The node is deleted
  • Implement class component status update function

Building an Array of Effects

The next thing we need to do is store all Fiber objects in an array, so why in an array?

Because in the second phase of the Fiber algorithm, we loop through this array to get the Fiber object uniformly to build the real DOM object, and mount the real DOM object in the page. So how do we build this array? This depends on the Effects array in each Fiber object.

The Effects array is used to store fiber objects, and our goal is to store all fiber objects in the Effects array of the outermost node. How can this be achieved?

As we know, all Fiber objects have an Effects array. The outermost Effects array of the fiber object stores all fiber objects. The other Effects arrays are responsible for collecting fiber objects. Finally, all the collected Fiber objects are aggregated into the Effects array of the outermost Fiber object.

Ideas:

After all the nodes of the left node species are built, we start a while loop to build the other nodes. We will find the parent fiber object of each node, so we can add a Fiber object to each node’s Effects array. To be precise, we merge the parent effects array with the child effects array, and the child Effects array is merged with the child Fiber. This array merge is going on in the while loop, and at the end of the loop, The outermost Effects array contains all fiber objects, because the loop is collecting fiber objects.

Code implementation:

const executeTask = fiber= >{...let currentExecutedFiber = fiber
  while(currentExecutedFiber.parent) {
    // there are sibling returns to sibling
    currentExecutedFiber.parent.effects = currentExecutedFiber.parent.effects.concat(
      currentExecutedFiber.effects.concat([currentExecutedFiber])
    )
    ...
  }
  console.log(currentExecutedFiber)
}

Copy the code

This completes the construction of the Effects array, and the outermost effects contains all fiber objects!!

Implement initial rendering

As long as we get the outermost Fiber object, we can get the Effects array that contains all the Fiber objects. The currentExecutedFiber variable stores the outermost fiber object

In the second phase of Fiber we are going to do real DOM manipulation, we are going to build relationships between DOM nodes, and after that we are going to add real DOM to the page.

How do you realize this requirement?

  • Step 1: Promote currentExecutedFiber as a global variable

The currentExecutedFiber variable is passed to the second-stage method when the second-stage render function is called. Only if you pass it to this method will you get the Effects array, and you can loop through the Effects array. Get the Fiber object formation for real DOM manipulation

// Wait to be committed
let pendingCommit = null

const commitAllWork = fiber= > {
  console.log(fiber.effects)
}

const executeTask = fiber= >{...let currentExecutedFiber = fiber
  while(currentExecutedFiber.parent) {
    // there are sibling returns to sibling. } pendingCommit = currentExecutedFiberconsole.log(currentExecutedFiber)
}

const jsx = (<div>
  <p>Hello React</p>
  <p>I'm the sibling child</p>
</div>)

Copy the code

  • Step 2: Traverse the currentExecutedFiber on the commitAllWork, manipulating the Fiber object

As you can see from the above JSX output Fiber object, the first one is the text node, so we get the Fiber object in reverse order, starting from the last child node on the left.

We first determine what type of effectTag each Fiber object has and then perform alignment, or node appending if it is placement.

const commitAllWork = fiber= > {
    fiber.effects.forEach((item) = > {
        if(item.effectTag === "placement") {
          item.parent.stateNode.appendChild(item.stateNode)
        }
   })
}
Copy the code

At this point, we finished the initial rendering, completing a very, very rudimentary Fiber algorithm.

Class component handling

1. Prepare a class component

We’ll start with a class Component that inherits from the Component class implemented in tinyReact. Create a new Component folder and create a Component class in the index.js file

// src/react/Component/index.js

export class Component {
  constructor(props) {
    this.props = props; }}class Creating extends Component {
  constructor(props) {
    super(props)
  }
  render() {
    return <div>hahhahaha</div>
  }
}

render(<Creating/>, root)

Copy the code

2. Build component tag attributes

As you can see from the figure, the fiber object’s tag is undefined, because in the getTag method we only deal with the case where type is string, so let’s deal with the component case

import { Component } from ".. /.. /Component"
const getTag = vdom= > {
  if(typeof vdom.type === 'string') {
    return "host_component"
  } else if(Object.getPrototypeOf(vdom.type) === Component) {
    return "class_component"
  } else {
    return "function_component"}}export default getTag

Copy the code

3. Create the stateNode attribute of the component

Let’s look at the picture. The stateNode property is undefined, and as we’ve seen before, if the DOM node is a normal element it stores a normal DOM instance object, if it’s a component it stores a component instance object. So the next step is to process the createStateNode to handle the component.

We create a createReactInstance folder in the Misc folder and inside the index.js file we create a method createReactInstance to handle the component and return the component instance object, which handles two types of component functions and class components

// src/react/Misc/createReactInstance/index.js
export const createReactInstance = fiber= > {
  let instance = null;
  if(fiber.tag === "class_component") {
    instance = new fiber.type(fiber.props)
  } else {
    instance = fiber.type
  }
  return instance
}
// src/react/Misc/createStateNode/index.js
import { createDOMElement } from '.. /.. /DOM'
import {createReactInstance} from ".. /createReactInstance"

const createStateNode = fiber= > {
  if(fiber.tag === "host_component") {
    return createDOMElement(fiber)
  } else {
    return createReactInstance(fiber)
  }
}

export default createStateNode
Copy the code

This completes the construction of the component’s stateNode

4. Optimize the executeTask method to handle component adding Fiber task

If it is a component, its children are not directly fiber.props. Children, but what is returned by the Render method in the reconcileChildren class component, so when calling the reconcileChildren method, you need to determine whether it is a component or a normal element. Is a component calls to render method (namely component instance renderfiber. StateNode. Render ()) in passed to reconcileChildren method,

const executeTask = fiber= > {
  if(fiber.tag === "class_component") {
    reconcileChildren(fiber, fiber.stateNode.render())
  } else {
    reconcileChildren(fiber, fiber.props.children)
  }
  
  ...
  }

Copy the code

This takes the child element of the component.

5. Render the class component DOM to the page on commitAllwork

The class component itself is also a node, but the node of the class component itself cannot append elements. So we need to look up the parent of the component until we find that the parent is not a component but a normal element and start appending elements

const commitAllWork = fiber= > {
  fiber.effects.forEach((item) = > {
    if(item.effectTag === "placement") {
      let fiber = item
      let parentFiber = item.parent
      // Loop over whether the parent tag is a component, and if so, look up
      while(parentFiber.tag === "class_component") {
        parentFiber = parentFiber.parent
      }
      // Append to the page as a normal element
      if(fiber.tag === "host_component") {
        parentFiber.stateNode.appendChild(item.stateNode)
      }
    }
  })
}
Copy the code

We successfully rendered the component content to the page!!

Processing of function components

To prepare, create a function component

function FnComponent() { 
  return <div>FnComponent</div>
}

render(<FnComponent/>, root)
Copy the code

Optimize the executeTask method to handle functional component tasks

We know that the stateNode of the function component stores the function of the function component, and the content of the function component can be obtained by calling the function component

const executeTask = fiber= > {
  if(fiber.tag === "class_component") {
    reconcileChildren(fiber, fiber.stateNode.render())
  } else if(fiber.tag === "function_component") {
    reconcileChildren(fiber, fiber.stateNode(fiber.props))
  }else {
    reconcileChildren(fiber, fiber.props.children)
  }
Copy the code

Optimize the commitAllWork method to handle function component content rendering to the page

Just like you would with a class component if it was a function component you would go all the way up until it wasn’t a component to append elements to the page

const commitAllWork = fiber= > {
  fiber.effects.forEach((item) = > {
    if(item.effectTag === "placement") {
      let fiber = item
      let parentFiber = item.parent
      while(parentFiber.tag === "class_component" || parentFiber.tag === "function_component") {
        parentFiber = parentFiber.parent
      }
      if(fiber.tag === "host_component") {
        parentFiber.stateNode.appendChild(item.stateNode)
      }
    }
  })
}
Copy the code

Function components render successfully, and class components and function components add attributes can also be obtained!!

OldFiber VS newFiber

Prepare a piece of test code

The React JSX and The React JSX are just replaced by the Olly. After dom initialization, we back up the old Fiber object, and after two seconds we call render, we create the node fiber object again. When creating a Fiber object, we check to see if the fiber node exists. If the old fiber object exists, we need to update it. After that we will create and update the Fiber node object.

const jsx = (<div>
  <p>Hello React</p>
  <p>Hello FIber</p>
</div>)
render(jsx, root)

setTimeout(() = > {
  const jsx = (<div>
    <p>cheer up</p>
    <p>Hello FIber</p>
  </div>)
  render(jsx, root)
}, 2000);
Copy the code

Optimize the commitAllWork method and add logic to back up old Fiber objects

const commitAllWork = fiber= > {
  fiber.effects.forEach((item) = >{...})/*** * Back up the old Fiber node object ***/
    fiber.stateNode.__rootFiberContainer = fiber
}
Copy the code

Optimize the getFirstTask method to add alterNate tags

When creating a new Fiber object, store the old Fiber object in alterNate and use it when building an updated Fiber object.

const getFirstTask = () = >{.../** * Returns the outermost node's fiber object */
  return{...alternate: task.dom.__rootFiberContainer
  }
}

Copy the code

Optimizing the reconcileChildren approach

In this method we need to determine what operations need to be performed on the Fiber object to build different types of operations on the fiber object

  • Step 1: Obtain the backup node

The reconcileChildren method is to build the reconcileChildren child nodes. In principle, we need to obtain the Fiber object for each child node. How do we do that?

Ideas:

If fiber. Alternate has the value, the backup node has the backup node, and the child node of the backup node is obtained. Fiber.alternate. If both have values, assign fiber.alternate. Child to alternate

Code implementation:

let alternate = null;

if(fiber.alternate && fiber.alternate.child) {
    alternate = fiber.alternate.child;
}
Copy the code

Fiber.alternate. child Finding this is also finding the standby subnode, whose backup node is actually the child node of the first node in the children array

  • The second step is to update the alternate for each child node

When the method is not iterated, the first loop is the first child, the second loop is to find the second child, the third one…

Then the alternate of the child node is updated, otherwise the alternate points to the backup node of the first child node

if(alternate && alternate.sibling) {
  alternate = alternate.sibling
} else {
// All sibling nodes have been found
  alternate = null
}
Copy the code

So in the reconcileChildren method, alternate is the old node, and element is the new node

  • Step 3: Determine the operation type

Should we do initial render or update or delete? By what judgment

If element exists alternate, do the initial render operation (this has been done before). If Element exists alternate, do the update operation, change the effectTag to Update, and add the alternate property. – Element and alternate if the type is the same, replace newFiber. StateNode with alternate. StateNode – Element and alternate if the type is different, NewFiber. StateNode value for createStateNode (newFiber)

const reconcileChildren = (fiber, children) = >{...while (index < numberOfElements) {
    element = arrifiedChildren[index];
    if (element && alternate) {
      /** * update */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [].effectTag: "update".stateNode: null.parent: fiber,
        alternate,
      };
      if (element.type === alternate.type) {
        /** * The same type */
        newFiber.stateNode = alternate.stateNode;
      } else {
        /** * Different types */
        newFiber.stateNode = createStateNode(newFiber);
      }

      newFiber.stateNode = createStateNode(newFiber);
    } else if(element && ! alternate) {/** * Initialize render */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [].effectTag: "placement".stateNode: null.parent: fiber, }; newFiber.stateNode = createStateNode(newFiber); }...}};Copy the code
  • Step 4: Perform DOM operations

The DOM is updated with the same efftctTag or with different efftctTag types

if(item.effectTag === "update") {
      /** * update */
      if(item.type === item.alternate.type) {
        /** * The node type is the same */

        updateNodeElement(item.stateNode, item, item.alternate)
      } else {
        /** * Different node types */
        item.parent.stateNode.replaceChild(item.stateNode, item.alternate.stateNode)
      }
    }
Copy the code

Step 5: Optimize the updateNodeElement function to handle not only regular element nodes but also text nodes if the type is the same. The current updateNodeElement function only handles regular element nodes, not text nodes

if(virtualDOM.type === "text") {
    if(newProps.textContent ! == oldProps.textContent) {if(virtualDOM.parent.type ! == oldVirtualDOM.parent.type) { virtualDOM.parent.stateNode.appendChild(document.createTextNode(newProps.textContent))
      } else {
        virtualDOM.parent.stateNode.replaceChild(
          document.createTextNode(newProps.textContent),
          oldVirtualDOM.stateNode
        )
      }
      
    }
    return
  }
Copy the code

Complete DOM update

The node is deleted

1. Preparation

Once again, prepare two paragraphs of JSX and delete a P tag after two seconds

const jsx = (<div>
  <p>Hello React</p>
  <p>Hello FIber</p>
</div>)
render(jsx, root)

setTimeout(() = > {
  const jsx = (<div>
    <p>Hello FIber</p>
  </div>)
  render(jsx, root)
}, 2000);

Copy the code

2. Optimize the reconcileChildren method to complete the deletion code

If element does not exist and alternate exists, you need to delete it. Notice that the arrifiedChildren array might be empty when you delete it, so the loop can’t get in and you need to change it

There are some areas that need to be optimized, that need to be improved, that element does not exist in the reconcileChildren cycle and that need to judge alternate should also enter the cycle

New nodes are added only when element exists

// Be more judgmental
  while (index < numberOfElements || alternate) {
    element = arrifiedChildren[index];
    
 / / add element
if (index === 0) {
  fiber.child = newFiber;
} else if(element){
  prevFiber.sibling = newFiber;
}
if(! element && alternate){/** * Delete operation */
      alternate.effectTag = "delete"
      fiber.effects.push(alternate);
    }
Copy the code

3. Optimize the commitAllWork and delete the DOM

const commitAllWork = (fiber) = > {
  fiber.effects.forEach((item) = > {
    if(item.effectTag === "delete") {
      item.parent.stateNode.removeChild(item.stateNode)
    }
  });
};

Copy the code

Remove it

Implement class component status update function

1. Preparation

Prepare a class component, set a state and then set a Name object

class Creating extends Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'Joe',}}render() {
    return <div>
      {this.props.title}hahhahaha{this.state.name}
      <button onClick={()= >This.setstate ({name: "l "})}>button</button>
      </div>
  }
}
render(<Creating title={"hello} "/ >, root)
Copy the code

2. Analyze and implement component status updates

When the component state is updated, it should be treated as a task and placed in a task queue. Also specify that the task queue is executed when the browser is idle. When executing tasks, we need to distinguish status update tasks from other tasks, so when adding tasks, we add an identity in the task object. We also need to add an instance object and the component state object to be updated to the task object. Because we want to get the original component state object in the component instance object. Because we can’t update the state object until we get the original component state object and the component state object that we’re updating.

One more question: how do you update the data from the state object into the real DOM object? We rebuild the Fiber object for each node, starting at the root node, to create the Fiber object that performs the update, which can then be applied to the formal DOM as the fiber phase 2 progresses.

Since we’re building a Fiber object from the root node, how do we get the generated Fiber object? The fiber object for the root node already exists when the component is updated. We will build a new root node fiber object from this existing fiber object.

We can back up the fiber object of the component to the instance object of the component. When the component state is updated, you can obtain the component instance object (stateNode). You can obtain the fiber object from the component instance object, and then you can look up the fiber object from the component instance object. Finally, you can obtain the outermost fiber object.

3. Code implementation

  • Implement setState

The setState method calls scheduleUpdate to pass two values. The first value is this, which represents the current component instance object, and the second value is partialState, which is the status of the update

import {scheduleUpdate} from ".. /reconciliation"
export class Component {
  constructor(props) {
    this.props = props;
  }
  setState(partialState) {
    scheduleUpdate(this, partialState); }}Copy the code
  • Realize the scheduleUpdate

As mentioned above, this method takes two arguments, the first instance (component instance) and the second parameter partialState. To complete the update, we override the state content in the component instance with the partialState content. We treat the component update as a task and place the task on the component queue.

export const scheduleUpdate = (instance, partialState) = > {
  taskQueue.push({
    from: "class_component",
    instance,
    partialState
  })
  requestIdleCallback(performTask)
}
Copy the code

When the component has been rendered to the page, the task queue is empty at this time. Click the button to save the component status update to the task queue, then there is only one task in the task queue, so the getFirstTask will be executed to obtain the state update task, so the task will be processed in this getFirstTask. In this method we are going to update the outermost Fiber object, and we are going to build on the existing Fiber object.

The fiber object is stored in the stateNode property of the commitAllWork.

 if(item.tag === "class_component") {
      item.stateNode.__fiber = item
    }
Copy the code

After setting the value, create a getRoot method in Misc to loop through the outermost fiber object


const getRoot = instance= > {
  let fiber = instance.__fiber;
  while(fiber.parent) {
    fiber = fiber.parent
  }
  return fiber
}

export default getRoot

Copy the code

Call this method inside getFirst and return an updated fiber instance

const getFirstTask = () = > {
  /** * Get the task */ from the task queue
  const task = taskQueue.pop();

  if(task.from === "class_component") {
    const root = getRoot(task.instance)
    task.instance.__fiber.partialState = task.partialState
    return {
      props: root.props,
      stateNode: root.stateNode,
      tag: "host_root".effects: [].child: null.alternate: root}} ···Copy the code

After obtaining the task, we will go to executeTask to execute the task, update the state in the task and put it into the state of the stateNode in Fiber

const executeTask = (fiber) = > {
  if (fiber.tag === "class_component") {
    if(fiber.stateNode.__fiber && fiber.stateNode.__fiber.partialState) { fiber.stateNode.state = { ... fiber.stateNode.state, ... Fiber. StateNode. __fiber. PartialState}}...Copy the code

Click the button to complete the update!!

conclusion

Today we have learned the second stage of Fiber, the understanding of the Fiber algorithm is complete, submit the Fiber object, complete the DOM to add to the page.

First we will create an Effects array, each node will have an Effects array, and we will merge the node’s own Fiber object with the effects array. Then we will merge this array into the parent Effects array, and finally all the Fiber objects will be inside the outermost Fiber object.

Then iterate through the effects array of the outermost Fiber object, add the child stateNode to the parent stateNode to complete the DOM tree construction, and finally add the DOM to the page. This is the simplest fiEBR algorithm. After that, we completed the processing of function components and class components, combined with the previous basic Fiber algorithm to optimize methods according to different types of components. For example, the sub-elements of class components are part of the Render method instead of stateNode. For example, the stateNode of function components is a method. Receive a TYPE to generate a DOM object. The outermost layer of the class component is ignored because the component tag does not need to be rendered to the page… The tag and stateNode attributes of different components can be optimized.

Finally, we need to complete the component update. In this case, we need to back up the previous Fiber object, so as to better complete the comparison between oldFiber and newFiber. When submitting the Fiber object to complete DOM construction, backup the outermost fiber.statenode.__rootFiberContainer = Fiber object, and then put the old outermost layer child node into the following alternate before the next update. When processing tasks, extract old fibers of all nodes through alternate. Element is the new FIeBR, alternate extraction is the old fiber. Element has and alternate has the update operation. Element does not have anything like alternate except the delete operation. After this is done, dom updates and DOM deletes are completed. Then there is the class component state update to complete dom updates, which we also treat as a special task. The task is added to the task queue when the state changes when the button is clicked, and a state update flag is given after it is added. Then, a new root fiber object is updated according to the new task. Finally, the state in the component instance is updated through the partialState, and the component state is changed to complete DOM update.

We have completed the whole FIber algorithm, we hope you can have a systematic understanding of FIber according to the article, thank you for your support, welcome to like and comment attention, we can progress the project code together, you can continue to follow up learning, mutual encouragement!!