The implementation of the related stack mediator for react learning (37)

Source: Internet
Author: User
Tags foreach bind constructor event listener extend

This section describes the implementation of the stack mediator

React API can be divided into three parts, the core , the renderer , the mediator , if you have a little knowledge of the code base, you can read my blog

The stack mediator is the most important part of the react product and is used in conjunction with the react DOM and react native renderer, and its code address is src/renderers/shared/stack/reconciler. 1. Build the history of react from scratch

Paul O ' Shannessy gave react a very big inspiration for the development of the final code base document reconciliation can be better understood, so there is time to see. 2. Overview

The mediator itself is not a public API, and the renderer acts as an interface through user-written react components that are effectively updated by the react Dom and react native two renderers. 3. Binding is recursion

The following is an instance of a binding component

Reactdom.render (<app/>, Rootel);

<App/> is a React object element that records how to render, React Dom passes <app/> As an object to the mediator, which you can think of as the following object

Console.log (<app/>);
{Type:app, props: {}}

If the app is a component class or function class, the mediator will check them.

If the app is a function, the mediator invokes the app (props) to render the element.

If the app is a class, the mediator instantiates an app object with the new app (props), then calls the Componentwillmount () life cycle method, and then invokes the render () method to render the element.

Either way, the mediator is able to know how to render the app elements.

This process is recursive, the app may render out the <greeting/>,greeting may render out the <button/>, and then continue to process the mediator by knowing how a component renders the interaction depth of the user-defined component.

You can think of this process as pseudo-code as follows:

The function IsClass (type) {///React.component subclasses will have these tags return (boolean (Type.prototype) && Boolean (type).
Prototype.isreactcomponent));
  }//This function handles the REACT element//Returns a DOM or native node function mount (Element) {var type = Element.type, which represents the tree that needs to be bound;

  var props = Element.props;
  We decide that the element to be rendered//it may be the result of a function run///It may also be a class instance calling render after the result of the run Var renderedelement;
    if (IsClass (type)) {//class component var publicinstance = new Type (props);
    Set props publicinstance.props = props;
    If necessary call the life cycle method if (Publicinstance.componentwillmount) {publicinstance.componentwillmount ();
  }//Call render to get the element to be rendered renderedelement = Publicinstance.render ();
  } else {//function component renderedelement = type (props);

  }//Because the component may return another component//So this process is a recursive process return mount (renderedelement); Note: This implementation is also incomplete because recursion is still not stopped/it can only handle custom composition components, such as <app/> or <button/>//and cannot handle host components such as <div/> or <p/&G
T } var Rootel = document.getElementById (' RooT ');
var node = mount (<app/>); Rootel.appendchild (node);

Note

The above is really a piece of pseudo-code, and the real implementation of the difference is too far, directly see the problem, recursion can not cut off, so the above is just a simple idea, the code needs to be perfected.

Let's summarize some of the key points of the above code:

The react element is represented as an object, which can be expressed by the type and props of the component, which is the task of react.createelement.

custom components (such as apps) can be classes or functions that return elements that need to be rendered

a binding is a recursive process that creates a DOM or native node tree 4. Binding the host element

If we do not show anything in the view, then the above code is useless, showing the result is our purpose.

In addition to user-defined composition components, the react element may also be a host component, such as a button that may return <div/> in the Render method.

If the type of the element is a string, we need to handle it according to the host element:

Console.log (<div/>);
{type: ' div ', props: {}}

When the mediator detects that it is a host element, it will let the renderer care how to bind it, for example, the React Dom renderer may create a DOM node, and the other renderer will create another, which is not the mediator's concern, here you can leave a mind, Really, the rendering process is always a renderer, and the mediator has no relationship with the half-dime.

If a host element has children, the mediator will recursively bind them in the same way (remember to bind instead of render), the child is host for host processing, and the child is the combination to be treated in the same way.

The DOM node is created by the child component and added to the Father Dom node, which is constantly recursively processed, and the final complete DOM structure is assembled.

Note

The mediator does not rely on the DOM itself, and the final result of the binding depends on the renderer, if the DOM node is the React Dom renderer, and if it is a string that is processed by the React Dom Server renderer, a number representing the native view is react Native renderer processing, these are not the mediators need to care.

If you extend the previous code to handle the host element, then the pseudo-code becomes the following form:

The function IsClass (type) {///React.component subclasses will have these tags return (boolean (Type.prototype) && Boolean (type).
Prototype.isreactcomponent));
}//This function is only used to deal with composition components///such as <app/> and <button/>, cannot handle <div/>.
  function Mountcomposite (Element) {var type = Element.type;

  var props = Element.props;
  var renderedelement;
    if (IsClass (type)) {//Class component instantiation var publicinstance = new Type (props);
    Set props publicinstance.props = props;
    Call life cycle function if (publicinstance.componentwillmount) {publicinstance.componentwillmount ();
  } renderedelement = Publicinstance.render ();
  } else if (typeof type = = = ' function ') {//function component renderedelement = type (props);
}//When the element we encounter is a host component and not a composition component//This recursive process will stop the return mount (renderedelement);

}//This function is only used to handle the host components///such as <div/> and <p/>, cannot handle <app/>.
  function Mounthost (Element) {var type = Element.type;
  var props = Element.props; var children = Props.children | |
  []; if (!
  Array.isarray (children)) {children = [children];

  } children = Children.filter (Boolean); This part of the code should not exist in the mediator//because different renderers initialize nodes differently///For example, React native will create iOS or Android view var node = document.createelement (t
  YPE); Object.keys (props). ForEach (propname = {if (propname!== ' children ') {Node.setattribute (propname, Props[pro
    PName]);

  }
  }); Bind child Children.foreach (childelement = =//child may be host or combination component//then recursively handle them var Childnode = mount (Childel

    Ement);
  This part of the code is also a special//form method depending on the different renderer node.appendchild (Childnode);

  });
Returns the DOM node as the result of the binding//recursive procedure returns the return node from here;
  } function mount (element) {var type = Element.type;
  if (typeof type = = = ' function ') {//user Custom Component return Mountcomposite (element);
  } else if (typeof type = = = ' String ') {//Host component return Mounthost (element);
}} var Rootel = document.getElementById (' root ');
var node = mount (<app/>); Rootel.appendchild (node);

The real mediator in this part of the code is still very remote, and it doesn't even have the update function. 5. Internal Instances

One of the key features of react is that you can render anything repeatedly, and this iterative rendering process does not recreate the DOM or reset the state, which is interesting, but not rendering, interesting.

Reactdom.render (<app/>, Rootel);
The following code does not have any performance penalty, because the following code is equivalent to no execution, interesting
reactdom.render (<app/>, Rootel);

However, our previous code is really the simplest way to construct the initial binding tree, because our previous code does not have a data reserve for the update phase, so we simply cannot implement the updated operation, such as publicinstances, or which component corresponds to which DOM node, This data is not known after the initialization of the binding, which is very embarrassing.

This stack-mediator code base solves it through a mount () function method in the class, which, of course, has some flaws, and we try to rewrite the mediator, but how does it work?

We no longer use the mounthost and Mountcomposite functions, we replace them with two classes: Domcomponent and compositecomponent, because objects can store data, and functions are generally pure functions that do not affect data reserves.

Each of the two classes has a constructor that accepts the element parameter and a mount method to return the bound node, and the global mount () function is replaced with the following code:

function Instantiatecomponent (Element) {
  var type = Element.type;
  if (typeof type = = = ' function ') {
    //user Custom Component
    return new compositecomponent (element);
  } else if (typeof type = = = ' string ') {
    //Host component
    return new domcomponent (element);
  }  
}

First, we first write the implementation of the Compositecomponent class:

Class Compositecomponent {Constructor (element) {this.currentelement = element;
    This.renderedcomponent = null;
  This.publicinstance = null; } getpublicinstance () {//For composite, expose the class instance. exposes its class instance to the composition component return This.publi
  Cinstance;
    } mount () {var element = This.currentelement;
    var type = Element.type;

    var props = Element.props;
    var publicinstance;
    var renderedelement;
      if (IsClass (type)) {//Component class Publicinstance = new type (props);
      Set props publicinstance.props = props;
      Call life cycle function if (publicinstance.componentwillmount) {publicinstance.componentwillmount ();
    } renderedelement = Publicinstance.render ();
      } else if (typeof type = = = ' function ') {//Function component does not have an instance publicinstance = null;
    Renderedelement = type (props);

    }//Save instance this.publicinstance = Publicinstance; Instantiate the child's internal instance based on the element//This instance may be domcomponent//It may also be compositecomponent var renderedcomponent = instantiatecomponent (renderedelement);

    This.renderedcomponent = renderedcomponent;
  Outputs the bound data to return Renderedcomponent.mount (); }
}

This is not much different from the implementation of the previous mountcomposite, but we have saved some useful information about our updated data, Like This.currentelement,this.renderedcomponent and This.publicinstance.

One thing to note, Our compositecomponent instances and user-instantiated element.type are not the same, compositecomponenet is the internal implementation of the mediator can not be used by the outside world, or is not exposed, you do not know it, and users themselves through the new Element.type () is not the same, you can directly deal with him, in other words, we implemented in the class Getpublicinstance () function is to let us get a public operation instance, but more low-level internal instances, we obviously can not operate, You can only manipulate the current layer as an instance exposed as a public interface.

To avoid confusion, I refer to compositecomponent and domcomponent as internal instances, where their presence can hold data for a long time, and only renderers and mediators can handle them directly.

In contrast, we refer to an instance of the user-defined class as a public instance (an external instance), which you can manipulate directly.

The Mounthost function is heavily composed of the Mount function in the domcomponent.

Class Domcomponent {Constructor (element) {this.currentelement = element;
    This.renderedchildren = [];
  This.node = null;
  } getpublicinstance () {return this.node;
    } mount () {var element = This.currentelement;
    var type = Element.type;
    var props = Element.props; var children = Props.children | |
    []; if (!
    Array.isarray (children)) {children = [children];
    }//Create and SAVE nodes var node = document.createelement (type);

    This.node = node; Set node properties Object.keys (props). ForEach (PropName = = {if (propname!== ' children ') {Node.setattribute (P
      Ropname, Props[propname]);

    }
    }); Create and save contained children//They may be domcomponent or compositecompoennt//depending on whether their type is a string or a function var Renderedchildren = C
    Hildren.map (instantiatecomponent);

    This.renderedchildren = Renderedchildren;
    Collect the node to bind var childNodes = renderedchildren.map (Child = Child.mount ()); Childnodes.foreach (Childnode = Node.appendchild (Childnode));
  Returns the DOM node as the result of the binding; }
}

The main difference between this and the previous code is that we refactored the Mouthost () and saved the current node and Renderedchildren to correlate the internal DOM component instances so that we can use them without a declaration.

In general, every internal instance, whether it's a combination or host, will now have a variable that points to their child's internal instance, thus constituting an internal instance chain, to illustrate him more graphically, if <App> is a functional component <Button> is a class component, and the button renders <div> the last internal instance chain as shown below.

[Object Compositecomponent] {
  currentelement: <app/>,
  publicinstance:null,
  renderedcomponent: [Object Compositecomponent] {
    currentelement: <button/>,
    publicinstance: [Object Button],
    renderedcomponent: [Object Domcomponent] {
      currentelement: <div/>,
      node: [Object Htmldivelement],
      renderedchildren: []
    }
  }
}

You will only see <div> in the DOM, but the internal instance tree includes both the combined internal instance and the host internal instance.

The combination of internal instances requires that you save something like this:

the current element

if the type of the element is a class, save the public instance

the current element is either Domcomponent or compositecomponent, and we need to save the internal instances of their rendering.

the host internal instance needs to be saved:

The current element

DOM node

All the kids inside the instance they could be domcomponent or maybe compositecomponent

You can imagine an internal instance tree how to build a complex application, React Devtools can give you a very intuitive result, it can be highlighted in gray host instance, with purple to highlight the combination of instances

However, if the final refactoring is to be done, we will also design a function to perform the actual binding operation like Reactdom.render (), which also returns a public instance where the preceding mount gets only the node to bind, not the actual binding.

function Mounttree (element, Containernode) {
  //create top internal instance
  var rootcomponent = instantiatecomponent (element);

  Add the top component to the final DOM container to implement the true binding
  var node = Rootcomponent.mount ();
  Containernode.appendchild (node);

  Returns the public instance
  var publicinstance = rootcomponent.getpublicinstance ();
  return publicinstance;
}

var Rootel = document.getElementById (' root ');
Mounttree (<app/>, Rootel);
6. Uninstall

Now that we have an internal instance that holds the child and DOM nodes, we can implement the uninstallation and, for composite Component, unload the recursive call lifecycle function.

Class Compositecomponent {

  //...

  Unmount () {
    //Call life cycle function
    var publicinstance = this.publicinstance;
    if (publicinstance) {
      if (publicinstance.componentwillunmount) {
        publicinstance.componentwillunmount ();
      }
    }

    Unload Render
    var renderedcomponent = this.renderedcomponent;
    Renderedcomponent.unmount ();
  }
}

For domcomponent, you need to tell every child to uninstall

Class Domcomponent {

  //...

  Unmount () {
    //Uninstall all children
    var renderedchildren = This.renderedchildren;
    Renderedchildren.foreach (Child = Child.unmount ());
  }
}

In fact, uninstalling the DOM component also requires removing the event listener and clearing the buffer, but I'll skip the details for a moment.

We now add a whole new global function Unmounttree (Containernode), and his function is similar to Reactdom.unmountcomponentatnode (). Contrary to the Mounttree function

function Unmounttree (containernode) {
  //read an internal instance from the DOM node
  //This _internalinstance internal instance we will add
  to it in Mounttree var node = containernode.firstchild;
  var rootcomponent = node._internalinstance;

  Unload the tree and clean the container
  rootcomponent.unmount ();
  containernode.innerhtml = ";
}

In order for the above code to work properly, we need to read a root internal instance for the DOM node, we modify Mounttree () to add a _internalinstance property to the Rootdom node, We're going to tell Mounttree that the existing tree should be destroyed so that it can be called multiple times:

function Mounttree (element, Containernode) {
  //destroys an existing tree
  if (containernode.firstchild) {
    Unmounttree ( Containernode);
  }

  Create a top-level internal instance
  var rootcomponent = instantiatecomponent (element);

  Add the rendered result of the top-level internal instance to the DOM
  var node = Rootcomponent.mount ();
  Containernode.appendchild (node);

  Save Internal instance
  node._internalinstance = rootcomponent;

  Returns a public instance of
  var publicinstance = rootcomponent.getpublicinstance ();
  return publicinstance;
}
7. Update

In the above, we implemented the uninstallation, however, if each time prop change to unload the old tree, the structural tree, react there is no need to exist, and the role of the mediator is to reuse the existing instances, so as to achieve performance improvements.

var Rootel = document.getElementById (' root ');

Mounttree (<app/>, Rootel);
The following statement is equivalent to no execution
of Mounttree (<app/> rootel);

We will extend our internal instances to implement a method that both domcomponent and compositecomponent need to implement a new function called receive (nextelement)

Class Compositecomponent {
  //...

  Receive (nextelement) {
    //...
  }
}

Class Domcomponent {
  //...

  Receive (nextelement) {
    //...
  }
}

The job of this function is to allow the component and its children to get to know the nextelement information in a timely manner and update it.

This section is described earlier as "virtual Dom diff", which allows each internal instance to receive updates by recursively walking down the internal instance tree. 8. Updating composition Components

When a composition component accepts a new element, we run the componeentwillupdate () life cycle function and then re-render the component with a new Porps to get a new rendering element.

Class Compositecomponent {

  //...

  Receive (nextelement) {
    var prevprops = this.currentElement.props;
    var publicinstance = this.publicinstance;
    var prevrenderedcomponent = this.renderedcomponent;
    var prevrenderedelement = prevrenderedcomponent.currentelement;

    Update your own element
    this.currentelement = nextelement;
    var type = Nextelement.type;
    var nextprops = nextelement.props;

    Derive the new render content
    var nextrenderedelement;
    if (IsClass (type)) {

      if (publicinstance.componentwillupdate) {
        publicinstance.componentwillupdate ( nextprops);
      }
      Update props
      publicinstance.props = nextprops;
      Re
      -Render nextrenderedelement = Publicinstance.render ();
    } else if (typeof type = = = ' function ') {
      Nextrenderedelement = Type (nextprops);
    }

    // ...

With Nextrenderedelement we can see the type of the rendered element, which is the same as when I was going to update it, and if the type doesn't change, then recursively down, without changing the current component.

For example, if render returns <button color= "Red" for the first time, and returns <button color= "Blue" for the second time, we can tell the corresponding internal instance to receive the new element.

   // ...

    If the rendered element type has not changed
    //Then re-uses the already existing instance, do not go to the newly created instance
    if (prevrenderedelement.type = = = Nextrenderedelement.type) {
      Prevrenderedcomponent.receive (nextrenderedelement);
      return;
    }

    // ...

However, if the newly rendered element is not the same as the previous element type, then we cannot do the update because <button> cannot be a <input>, so We had to unload the existing internal instance and then load the corresponding new render element:

// ...

    Get the old node
    var prevnode = Prevrenderedcomponent.gethostnode ();

    Uninstall the old child to load the new child
    Prevrenderedcomponent.unmount ();
    var nextrenderedcomponent = instantiatecomponent (nextrenderedelement);
    var nextnode = Nextrenderedcomponent.mount ();

    Replace reference
    this.renderedcomponent = nextrenderedcomponent;

    Note: This part of the code should theoretically be placed outside compositecomponent instead of inside
    prevNode.parentNode.replaceChild (NextNode, PrevNode);
  }
}

In summary, a composite component receives a new element, and he either updates the internal instance directly, or unloads the old instance, loading the new instance.

There is another case where the component is reloaded rather than accepting a new element when the key of an element is changed, and of course we do not discuss the change caused by the key, because it will be more complex.

It is important to note that we need to add a function called Gethostnode () for the internal instance so that the specified node is found and then updated in the update phase, following its simple implementation in two classes:

Class Compositecomponent {
  //...

  Gethostnode () {
    //recursively process
    return This.renderedComponent.getHostNode ();}
}

Class Domcomponent {
  //...

  Gethostnode () {
    return this.node;
  }  
}
9. Updating the host component

The implementation of the host component is like domcomponent, unlike the compositecomponent update, when they receive an element, they need to update the underlying DOM nodes, and in the case of react DOM, they update the DOM properties:

Class Domcomponent {
  //...

  Receive (nextelement) {
    var node = this.node;
    var prevelement = this.currentelement;
    var prevprops = prevelement.props;
    var nextprops = nextelement.props;    
    This.currentelement = nextelement;

    Removes the old property
    Object.keys (Prevprops). ForEach (propname = = {
      if (propname!== ' children ' &&!). Nextprops.hasownproperty (propname)) {
        node.removeattribute (propname);
      }
    });
    Sets the new property
    Object.keys (Nextprops). ForEach (propname = = {
      if (propname!== ' children ') {
        Node.setattribute (PropName, Nextprops[propname]);
      }
    );

    // ...

The host component then starts to update their children, unlike the composite components, which only contain up to one child.

In the following example, I'm using an internal instance of an array, and then traversing it, or updating or replacing the inner instance, depending on whether the new element and the old element's type are the same. The real mediator will also use the key of the element to deal with, of course we will not involve this processing logic for the time being.

We can collect the children that the DOM node needs to manipulate and then process them in batches to save time:

// ...

    Here the react element is an array
    
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.