when we are proficient in using React for front-end development, we will inevitably have a strong interest in the internal mechanism of React. What are the components? Is it the real DOM? What is the basis for the execution of lifecycle functions?
in this article, let's first study the implementation and mounting of React components.
1. What is the component
first write the simplest component:
after the above code is written, we get the & lt;A / & gt;
component, so let's figure out what & lt;A / & gt;
is. Print it out with console.log
:
you can see that & lt;A / & gt;
is actually a js object rather than a real DOM. Notice that props
is an empty object at this time. Next, let's print & lt;A>
, which is component A & lt;/div> , and see what the console outputs:
We see that props
has changed. Because div
and div
are nested in the & lt;A / & gt;
component, the children
attribute is added to the props
describing the & lt;A / & gt;
object, whose value is the js object describing div
. By the same token, if we do multi-level component nesting, we are actually adding children
fields and corresponding description values to the props
of the parent object, that is, the multi-level nesting of js objects.
the above description is based on the React development model of ES6. In fact, the components created by the React.createClass ({})
method in ES5 are exactly the same as those in ES6, and can also be verified by printing the results of the components on the console. I will not repeat them here.
so how is the React component that looks like a HTML tag actually an object?
because our component declaration is based on React
and Component
, we first open React.js
and see the following code:
when we import React from 'react'
, we introduce the React object provided in the source code. When extends Component
, the Component
class is inherited. Two points need to be explained here:
- Why can it be successfully introduced when
module.exports
is clearly used in the source code instead ofexport default
? This is actually the work of the babel parser. It makes(ES6) import = = (CommonJS) require
. In typescript, a strictexport default
declaration is required, soimport React from 'react'
cannot be used under typescript. Interested readers can give it a try. - We can write
extends Component
orextends React.Component
. Is there any difference between the two? The answer is no. BecauseComponent
is a reference toReact.Component
. That is to say,Component = = React.Component
, you can write whatever you want in the actual project.
follow the clue of ReactComponent
, we open node_modules/react/lib/ReactComponent.js
:
the above code is the most familiar constructor, and you must be familiar with it. At the same time, we also note that setState
is defined as a method with two parameters on the prototype, which we will explain in the chapter on the React update mechanism.
the above code shows that component A, which we declared at the beginning, is actually a subclass of the ReactComponent
class, and its prototype has methods such as setState
. In this way, component An already has the most basic prototype.
2. Initialization of components
After
declares A, we can customize methods internally, or we can use lifecycle methods, such as ComponentDidMount
, which are exactly the same as when we write "classes". The only difference is that the component class must have render
method output similar to & lt;div>, which is the structure of component A & lt;/div>
and mounted on the real DOM in order to trigger the component's life cycle and become part of the DOM tree. First, let's look at how the "class" of ES6 initializes a react component.
put the original sample code into babel:
where _ Component
is the object ReactComponent
, and the _ inherit
method is the functional implementation of the extends
keyword. These are all ES6-related contents, which we will ignore for the time being. The point is that we find that the render
method actually calls the React.createElement
method (actually the ReactElement method). Then we open ReactElement.js
:
when we see here, we find that virtually every component object is an object of type ReactElement
created through the React.createElement
method. In other words, ReactElment
is an object that internally records the characteristics of the component and tells React what you want to see on the screen. In ReactElement
:
parameters | function |
---|---|
? typeof |
identification information of the component |
key |
DOM structure identification to improve update performance |
props |
substructure related information (add children field / not empty) and component properties (such as style ) |
ref |
reference to the real DOM |
_ owner |
_ owner = = ReactCurrentOwner.current (ReactCurrentOwner.js), the value is the object that created the current component, and the default value is null. |
after reading the above, I believe you already have some understanding of the essence of React components. The js object of type ReactElement
created by executing React.createElement
is the "React component", which corresponds exactly to the result printed by the console. To sum up, if we declare React components through the class
keyword, they are js objects of type ReactElement
until they are resolved to real DOM.
Summary
supplement the previous mind map:
3. Mounting of components
We know that custom components / native DOM/ strings can be mounted in the form of ReactDOM.render (component,mountNode)
,
so how is the mount process implemented?
ReactDOM.render
actually calls the internal ReactMount.render
, and then executes ReactMount._renderSubtreeIntoContainer
. Literally, you can see the logic of inserting "sub-DOM" into the container. Let's take a look at the source code implementation:
this code is very important, and all the functions of the render
function are here (click on the picture for a larger picture).
Let's first parse the parameters passed into _ renderSubtreeIntoContainer
:
when rendering for the first time
parameters | function |
---|---|
parentComponent |
the parent component of the current component, null |
nextElement |
the component to be inserted into the DOM, such as helloWorld |
container |
the container to be inserted, such as document.getElementById ('root') |
callback |
callback function after completion |
the functions of these parameters are easy to understand, so let's do a logical analysis line by line:
- line 2: add the current component to the
props
attribute of the previous level. (it has been stated at the beginning of this article that the parent-child nesting relationship is provided byprops
) - line 4-22: call the
getTopLevelWrapperInContainer
method to determine whether a component exists under the current container, and mark it asprevComponent
; if so, theprevComponent
istrue
, and the update process is executed, that is, the_ updateRootComponent
method is called. If it does not exist, uninstall it. (callunmountComponentAtNode
method) - line 24: whether it is updated or uninstalled, it will eventually be mounted on the real DOM. Look at the source code of
. _ renderNewRootComponent
:
analyze the flow:
The instantiateReactComponent
wrapper method appears in line 3 of
-
- , which we'll talk about later.
In line 5 of
- ,
batchedMountComponentIntoNode
callsmountComponentIntoNode
in the form of a transaction (the transaction will be parsed with a special article). This method returns the HTML corresponding to the component, marked as the variablemarkup
. AndmountComponentIntoNode
finally calls_ mountImageIntoNode
. Take a look at the source code:
the core code is the last two lines. setInnerHTML
is a method that sets markup
to the innerHTML
property of container
, which completes the insertion of DOM. The precacheNode
method is to store the processed component object in the cache to speed up the structure update.
the process of initializing and mounting React components is basically clear here. In the use of the ReactDOM.render ()
method, we will notice that this method can mount the React component, the string, or the native DOM. We now know that mounting makes use of the innerHTML
attribute, but does React handle different element structures differently?
As we mentioned earlier, in the penultimate step of component mounting, which is the execution of the _ renderNewRootComponent
method, we see a method called instantiateReactComponent
that returns a processed object. Let's look at the source code of instantiateReactComponent
:
the passed parameters node
are the component parameters of the ReactDOM.render
method. Input node
and output instance
can be summarized as follows:
node |
actual parameters | result |
---|---|---|
null / false |
empty | create ReactEmptyComponent component |
object & & type = = string |
Virtual DOM | create ReactDOMComponent component |
object & & type! = string |
React components | create ReactCompositeComponent component |
string |
string | create ReactTextComponent component |
number |
numbers | create ReactTextComponent component |
sort out the process:
- according to the different parameters passed in
ReactDOM.render ()
, four types of wrapper components will be created inside the React, marked ascomponentInstance
. - is then passed as a parameter in the
mountComponentIntoNode
method, from which the corresponding HTML of the component is obtained, which is recorded as the variablemarkup
. - the DOM insertion is completed by setting the property
innerHTML
of the real DOM tomarkup
.
so the question is, how do you parse HTML in the second step above? The answer is that in the first step of encapsulating into four types of components, the mountComponet
method is given to the encapsulated component, and the execution of this method triggers the lifecycle of the component, thus parsing the HTML.
of course, the most commonly used of these four categories of components is the ReactCompositeComponent
component, that is, the React component, which has a complete life cycle and is the most critical component feature of React. We will explain the detailed component types and lifecycles in the next article.
4. Summary
use a diagram to sort out the flow of React components from declaration to initialization to mount: