JSX
React users are already familiar with JSX, but do you really understand how it works?
Let’s take it from the shallow to the deep and uncover the true face of JSX layer by layer.
The code address in the article is at 👇.
React.createElement
JSX syntax will eventually be compiled by Babel into the react.createElement() method.
Let’s look at this JSX
<div className="wang.haoyu">hello</div>
Copy the code
When compiled by Babel, it looks like this:
React.createElement("div", {
className:'wang.haoyu'
}, "hello");
Copy the code
When there are multiple node elements in JSX, such as:
<div>hello<span>world</span></div>
Copy the code
It passes the children property in JSX of multiple nodes as multiple arguments:
React.createElement("div".null."hello", React.createElement("span".null."world"));
Copy the code
As you can see, the children elements wrapped around the outer divs are arranged in the react. createElement, not in a tree structure.
Note that the React package was introduced in the old React version whenever we used JSX. And the variable introduced must be capitalized React, since Babel looks for the React variable after compiling JSX.
In the new version, we no longer need to introduce the React variable. Object(s.js)(“div”,{children: “Hello”}). The older version was react.createElement (‘div’,null,’Hello’).
The effect and principle of the two methods are exactly the same, but the new version of the extra import package to handle the import. So you don’t need to introduce React alone.
React
The element
The React element is the smallest unit to build React, which is essentially a virtual Dom object.
JSX is essentially executing a function call, in factory mode, with react. createElement returning an element.
const element = <div>Hello</div>
console.log(element,'element')
Copy the code
Ignoring some ref/key attributes, we see that it is actually a JS object that records the type of the element. Props represents the accepted prop of the element, and note that the JSX internal tag content is inserted into the children property of props.
Notice the children property here, if the inner tag element has more than one child. Children is going to be an array. Because there are only text nodes, there is only one Hello.
When we use the React project, there is always a code in index.tsx that looks like this:
ReactDOM.render(<App />.document.getElementById('root'));
Copy the code
ReactDOM. Render creates a virtual DOM node based on the React. CreateElement method and inserts the DOM into the node. This is the simple rendering process.
Update of elements
react
The element in is itself immutable.
Such as:
const element = <h1 title="hello" >Hello</h1>
console.log(JSON.stringify(element,null.2))
Copy the code
When we want to change it to world, if we just go through
element.props.children = 'world'
Copy the code
React will prompt you:
Uncaught TypeError: Cannot assign to read only property 'children' of object '#<Object>'
Copy the code
It is not possible to assign a read-only attribute to children, nor to modify other attributes such as type.
When we add attributes to react elements in this way, we also add.
Cannot add property xxx, object is not extensible
Copy the code
Not Extensible was added after React17. Use object.freeze () to process the Object elements.
Note that Object.freeze() is a shallow freeze and is recursive object.freeze () inside react.
So react elements themselves are immutable and cannot be changed after they are created. An old element can only be updated by recreating a new one.
As you can see, every element in React is like every frame in an animation, which cannot be changed.
Of course, the React update only updates the content that needs to be updated. The internal diff algorithm is performed in the same way as Vue, efficiently updating the changed elements instead of updating and re-rendering all elements.
jsx
The principle of analysis
Note that we use the old react. createElement method. In ^17, you need to add DISABLE_NEW_JSX_TRANSFORM=true to the environment variable.
Now that we’ve analyzed the return value of the react. createElement method, we’ll try to implement JSX rendering ourselves.
React createElement ();
import React from 'react';
import ReactDOM from 'react-dom';
const element = (
<div className="header" style={{ color: 'red' }}>
<span>hello</span>world
</div>
);
console.log(JSON.stringify(element, null.2), 'element');
Copy the code
Let’s implement a simple createElement method by deducting the result
implementationReact.crateElement
Method – NativeDOM
Element rendering
- implementation
utils/react.js
// Add a wrapToDom element to make it easier to compare react source code
// We call common types Object for convenience
const React = {
createElement: function (type, config, children) {
constprops = { ... config, };// Babel compiles JSX
// If the parameter is greater than 3 then there are multiple children props. Children is an array
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments.2);
} else {
props.children = children;
}
return{ type, props, }; }};export default React;
Copy the code
This step we have implemented the basic React. CreateElement method.
index.tsx
import React from './utils/react';
import ReactDOM from 'react-dom';
// the compiled Babel code will introduce react. createElement
// React refers to our own React
const element = (
<div className="header" style={{ color: 'red' }}>
<span>hello</span>world
</div>
);
ReactDOM.render(element, document.getElementById('root'));
Copy the code
implementationReactDOM.render
Methods –react
The native DOM element is inserted into the page as a real element
- And then let’s implement a first for
children
The method of determining the type
// utils.js
// Always judge the text type
const REACT_TEXT = Symbol('REACT_TEXT')
// Whatever element was previously converted to VDOM object form
function transformVom(element) {
// Extra processing of text nodes outputs the same type of text node as other nodes
if(typeof element === 'string' || typeof element === 'number') {
return { type: REACT_TEXT, props: { content: element } }
}
return element
}
Copy the code
- Now let’s modify what we wrote before
React.createElement
methods
import { transformVNode } from './utils';
const React = {
createElement: function (type, config, children) {
constprops = { ... config, };if (arguments.length > 3) {
props.children = Array.prototype.slice
.call(arguments.2)
.map(transformVNode);
} else {
props.children = transformVNode(children);
}
return{ type, props, }; }};export default React;
Copy the code
- And now we have the corresponding
VDom
Object, and you can start implementing itReact.render
Methods.
The core idea of React. Render is to mount tag nodes on the corresponding elements that our Vdom object programming browser can recognize.
/** * insert the virtual DOM into the real DOM@param {Object} VDom Virtual DOM *@param {HTMLElement} El elements * /
import { REACT_TEXT } from './constant';
// The real render method
function render(vDom, el) {
const newDom = createDom(vDom);
el.appendChild(newDom);
}
// Forget about custom components
function createDom(vDom) {
const { type, props } = vDom;
let dom;
// Text node
if (type === REACT_TEXT) {
dom = document.createTextNode(props.content);
} else {
dom = document.createElement(type);
}
// Update attributes
if (props) {
// Update node Dom attributes
updateProps(dom, {}, props);
// Handle children with undefined/null and do nothing
// Consider that children is an array
// Consider that children is an Object, then it has only one son node
if (typeof props.children === 'object' && props.children.type) {
// Single element
render(props.children, dom);
} else if (Array.isArray(props.children)) {
// Multiple elementsreconcileRender(props.children, dom); }}// Record the mounted node
vDom.__dom = dom;
return dom;
}
// Mount multiple DOM elements react. createElement regardless of recursion
function reconcileRender(vLists, parentDom) {
for (let node ofvLists) { render(node, parentDom); }}/** * insert the virtual DOM into the real DOM@param {HTMLElement} Dom elements *@param {Object} The props of the oldProps element itself is used for updates@param {Object} NewProps element newProps */
function updateProps(dom, oldProps, newProps) {
// merge props for now no old, just new
Object.keys(newProps).forEach((key) = > {
if (key === 'children') {
// Handle children alone
return;
}
if (key === 'style') {
addStyleToElement(dom, newProps[key]);
} else if (key === 'content') {
// The text does nothing
} else{ dom[key] = newProps[key]; }}); }function addStyleToElement(dom, styleObject) {
Object.keys(styleObject).forEach((key) = > {
const value = styleObject[key];
dom.style[key] = value;
});
}
const ReactDOM = {
render,
};
export default ReactDOM;
Copy the code
The core idea here is to recursively mount the virtual DOM using the Render method to convert the corresponding properties into real DOM nodes and render it on the page using appendChild.
Write in the last
So far, we’ve basically achieved thatReact.createElement
andReactDom.render
These two methods. But for now, it’s only for primary studentsDOM
The node is processed.
inReact
We know that there will be a variety of components that we define ourselves, and we will take a step by step look at the rendering flow of these components.
Function Component
React renders and mounts native DOM nodes. Now let’s look at rendering the Function Component.
Of course, let’s look at the results of the Function Component rendering first.
import React from 'react';
import ReactDOM from 'react-dom';
interface IProps {
name: string;
}
const MyComponent: React.FC<IProps> = (props) = > {
return (
<div>hello<p>{props.name}</p>
</div>
);
};
const element = <MyComponent name="wang.haoyu" />;
console.log(element);
Copy the code
In the above code we created a function component and used it to assign a value to Element. Let’s see what it prints:
At this point we can see that relative to a normal DOM node. Differences between purely functional components:
$$typeof
forSymbol(react.element)
Indicates that the type of the element node is a purely functional component.- In a normal DOM node,
type
The type is the corresponding label type. When it is a purely functional component.type
The type is the function itself.
It is important that the type of the component is the function itself.
I’m sure you’ve seen how the element pure function component element should be converted into the VDOM object above. Let’s look at the compilation of Babel again:
As you can see, JSX for purely functional components is ultimately compiled to
const element = /*#__PURE__*/React.createElement(MyComponent, {
name: "wang.haoyu"
});
Copy the code
Modify the React. CreateElement method
The first parameter is type, the second parameter is props, and the third parameter is children…
We found that the react. createElement method we wrote earlier doesn’t need to be modified in pure function components:
import { transformVNode } from './utils';
const React = {
createElement: function (type, config, children) {
constprops = { ... config, };if (arguments.length > 3) {
props.children = Array.prototype.slice
.call(arguments.2)
.map(transformVNode);
} else {
props.children = transformVNode(children);
}
return{ type, props, }; }};export default React;
Copy the code
Passing in a pure function component still returns the correct result (leaving out the $$typeof result for now). Returns the type attribute of the virtual DOM pointing to itself, and config is the props passed in. The remaining children are mounted as properties on props. Children.
In fact, the displayName,defaultProps that we use are all properties that are mounted on this function itself. You can also see why we usually use xxx.type.displayName when we need to get these properties.
Modify the reactdom.render method
Since the react. createElement method doesn’t need to be modified at all. So let’s look at the corresponding reactdom.render method.
At this point our Render method expects us to pass in a custom function component, and ReactDOM will convert our custom component into a real DOM for mounting.
Let’s analysis analysis, through the React. The createElement method (FunctionCompoent, props, children). The type passed in is a function in itself that returns a JSX object.
If that still doesn’t make sense to you, you can interpret this code like this:
const MyComponent: React.FC<IProps> = (props) = > {
return (
<div>hello<p>{props.name}</p>
</div>
);
};
Copy the code
The above JSX code is converted by Babel at compile time to code like this:
const MyComponent = props= > {
return /*#__PURE__*/React.createElement("div".null."\u4F60\u597D,"./*#__PURE__*/React.createElement("p".null, props.name));
};
Copy the code
You can make a lot more sense by thinking of the result returned by the MyComponent call as an object (which is essentially an object) :
Note: This returns a call to the react. createElement function, which returns a dummy DOM object.
To make this clear, let’s try rewriting the reactdom.render method:
/** * insert the virtual DOM into the real DOM@param {Object} VDom Virtual DOM *@param {HTMLElement} El elements * /
import { REACT_TEXT } from './constant';
// The real render method
function render(vDom, el) {
const newDom = createDom(vDom);
el.appendChild(newDom);
}
// Forget about custom components
function createDom(vDom) {
const { type, props } = vDom;
let dom;
// Text node
if (type === REACT_TEXT) {
dom = document.createTextNode(props.content);
} else if (typeof type === 'function') {
// If it is a pure function, the component type is a function and running the function returns a virtual DOM object
// The createDom method is used to recursively convert the virtual DOM to the real DOM
return mountFunctionComponent(vDom);
} else {
dom = document.createElement(type);
}
// Update attributes
if (props) {
// Update node Dom attributes
updateProps(dom, {}, props);
// Handle children with undefined/null and do nothing
// Consider that children is an array
// Consider that children is an Object, then it has only one son node
if (typeof props.children === 'object' && props.children.type) {
// Single element
render(props.children, dom);
} else if (Array.isArray(props.children)) {
// Multiple elementsreconcileRender(props.children, dom); }}// Record the mounted node
vDom.__dom = dom;
return dom;
}
// Mount functional components
function mountFunctionComponent(vDom) {
const { type, props } = vDom;
// type(props) executes FunctionComponent to return the corresponding virtual DOM object
// The createDom method is used to convert the virtual DOM to the real DOM
return createDom(type(props));
}
// Mount multiple DOM elements react. createElement regardless of recursion
function reconcileRender(vLists, parentDom) {
for (let node ofvLists) { render(node, parentDom); }}/** * insert the virtual DOM into the real DOM@param {HTMLElement} Dom elements *@param {Object} The props of the oldProps element itself is used for updates@param {Object} NewProps element newProps */
function updateProps(dom, oldProps, newProps) {
// merge props for now no old, just new
Object.keys(newProps).forEach((key) = > {
if (key === 'children') {
// Handle children alone
return;
}
if (key === 'style') {
addStyleToElement(dom, newProps[key]);
} else if (key === 'content') {
// The text does nothing
} else{ dom[key] = newProps[key]; }}); }function addStyleToElement(dom, styleObject) {
Object.keys(styleObject).forEach((key) = > {
const value = styleObject[key];
dom.style[key] = value;
});
}
const ReactDOM = {
render,
};
export default ReactDOM;
Copy the code
We can see that we have made a small change to the createDOM method to determine if the type of the vDom passed in is a function:
- The incoming
vDom
theprops
, run this function. Get the returnedvDom
Object. - get
vDom
Object after passing through the previouscreateDom
Method will bevDom
Returns a real node. - At this time
render
Method to get the corresponding generated realityDOM
Object to mount inDOM
Element.
This is essentially recursive. If it’s a function, run the function to the returned vDOM, and then use createDom to convert the vDOM to the corresponding real DOM mount.
About JSX converting to React. CrateElement (…) Babel is doing the translation of this layer. Follow up on the principles of compilation.
You can also see why the JSX returned by React must require a wrap element for the outermost element.
The reactdom. render method accepts the Element passed in. When there is only one root node in the inner layer, for example
const element = <div>This is <p>me</p>.<p>My name is wang.haoyu</p></div>
Copy the code
The compiled:
React.createElement(
'div',
null,
'This is ',
React.createElement('p', null, 'me'),
'.',
React.createElement('p', null, 'My name is wang.haoyu')
);
Copy the code
Reactdom. render is handled by passing in a React virtual node. The real DOM is generated by the virtual node, and then its children are recursed layer by layer. The virtual node of children is generated by the createDOM method to generate the corresponding real DOM, and then mounted on the corresponding parent node DOM.
One idea: the reactdom.render () method only supports passing in a VDOM object and an EL. Its role is to mount the VDOM generated real DOM on the EL. If the VDOM has some children, then reactdom. render recurses its children and mounts the DOM node generated by children on parentDom. Layer by layer to mount.
forFunctionComponet
In thechildren
The rendering of
To get a better understanding of React and ReactDOM, let’s walk through our own process of writing React and ReactDOM. When I pass in code like this:
function MyComponent(props) {
return (
<div>
<p>hello</p>
<p>{props.children}</p>
</div>
);
}
const element = (
<MyComponent name="wang.haoyu">
<div>hello</div>
<p>hello</p>
</MyComponent>
);
ReactDOM.render(element)
Copy the code
So, first of all, the code that we just did, after Babel, will look like this
const element1 = React.createElement(
MyComponent,
{
name: 'wang.haoyu',
},
React.createElement('div'.null.'\u4F60\u597D'),
React.createElement('p'.null.'hello'));Copy the code
Next we’ll go to our react. createElement, which transforms the MyComponent function. Will be converted to an object that looks like this:
const element = {
props: {
name: 'wang.haoyu'.children: [{
type: 'div'.props: {
children: {
type: Symbol(text)
content: 'hello'}}}, {type: 'p'.props: {
children: {
type: Symbol(text)
content: 'hello'}}}]},type: MyComponent
}
Copy the code
Then we call Renderdom. render(Element,el) to render. The first step is to enter the createDOM function and find that its type is a function component. Our logic would be to run the function component, passing in its props. At this point, we can clearly see the structure of type(props).
Call this function component and pass in the corresponding props.
When we call this function component, we return a JSX, which we’re used to. The resulting JSX is translated into a VDOM object:
function MyComponent(props) {
return (
<div>
<p>hello</p>
<p>{props.children}</p>
</div>
);
}
Copy the code
The converted VDOM object (omits the react. crateElement and prints the VDOM result directly):
- First entry
createDOM
And found out that he wasFC
Type. To their ownprops
Calls itselfFC
Function. - After the function is run, it is called recursively
createDOM
At this time,VDOM
Has become such an object.
{
type: 'div'.props: {
children: [{type: 'p'.props: {
children: {
type: Symbol('text'),
content: 'hello',},},}, {type: 'p'.props: {
// Notice the props. Children here
// Props is the props passed in when the function component is called
children: props.children,
},
},
],
},
};
Copy the code
Here’s what it should be:
{
type: 'div'.props: {
children: [{type: 'p'.props: {
children: {
type: Symbol('text'),
content: 'hello',},},}, {type: 'p'.props: {
// This is essentially equivalent to props. Children
// children: props.children
children: [{type: 'div'.props: {
children: {
type: 'Symbol(text)'.content: 'hello',},},}, {type: 'p'.props: {
children: {
type: 'props.children'.content: 'hello',},},},],},},};Copy the code
Simply put, the elements inside the component tag. Pass it as a translated object directly into the FunctionComponent to call its own function and get the returned JSX to get the new VDOM object returned.
It’s not really that hard, it’s just a partial detour. If you don’t understand it, try writing it by hand a few times and follow the debugger. After a period of time to look at deepen memory will be very clear.
Write in the last
For beforeReact
In about< MyComponet > hello < / Mycomponent >
Rendering this code with current logic is a bit confusing. I’ll sort it out and summarize it laterdebugger
Detailed process.- After combing the above process, we will talk about it
React
In aboutclass
Component rendering.
class
Component rendering
class
Component preliminary analysis
React has no plans to remove class components in the near term, although react17 strongly recommends using hooks instead of class components. And in some cases class components are necessary, and there’s a lot of react knowledge involved in class components. So let’s look at the react implementation of the class component.
React internal components are divided into source components and custom components.
- The source component passes
babel
The compiledVDOM
thetype
The attribute type is a string representing the node label of the current element. - Custom components are compiled
type
A function that points to itself.
There is no such thing as a class in the javascript world, and classes are just syntactic candy for function.
Let’s look at the result of compiling the class component:
CreateElement (ClassComponent,{name:”wang.haoyu”}). The type passed in (the first argument) is also the class component itself. (function)
The class component and the Function component type attribute are both functions. So how do you distinguish between a component and a function component?
In React, the class component inherits from react.component.isreactComponent. This property is unique to class components, not function components, and allows the classification of components from functional components.
Let’s try accessing this property to see:
The difference between a class component and a function component is that the class component has the isReactComponent property on its prototype.
The isReactComponent value on the class component is an empty object {}, just to denote it.
implementationclass
Component rendering
Next we will implement the rendering process of classComponet.
First, let’s implement the parent class react.component.react.ponent.
transformReact.js
file
We’ll add a Component property to the React object:
import { transformVNode } from './utils';
import { Component } from './component';
const React = {
Component,
createElement: function (type, config, children) {
constprops = { ... config, };if (arguments.length > 3) {
props.children = Array.prototype.slice
.call(arguments.2)
.map(transformVNode);
} else {
props.children = transformVNode(children);
}
return{ type, props, }; }};export default React;
Copy the code
Let’s create a new Component file to implement the Component class:
// component.js
class Component {
constructor(props) {
this.props = props; }}// Implement class component-specific properties
Component.prototype.isReactComponent = {};
export { Component }
Copy the code
Let’s modify the react. render method to support class component rendering:
/** * insert the virtual DOM into the real DOM@param {Object} VDom Virtual DOM *@param {HTMLElement} El elements * /
import { REACT_TEXT } from './constant';
// The real render method
function render(vDom, el) {
const newDom = createDom(vDom);
el.appendChild(newDom);
}
// Forget about custom components
function createDom(vDom) {
const { type, props } = vDom;
let dom;
// Text node
if (type === REACT_TEXT) {
dom = document.createTextNode(props.content);
} else if (typeof type === 'function') {
if (type.prototype.isReactComponent) {
// There is an isReactComponent attribute on the prototype
return mountClassComponent(vDom);
} else {
// If it is a pure function, the component type is a function and running the function returns a virtual DOM object
The createDOM method essentially converts a virtual DOM object into a real DOM return
returnmountFunctionComponent(vDom); }}else {
dom = document.createElement(type);
}
// Update attributes
if (props) {
// Update node Dom attributes
updateProps(dom, {}, props);
// Handle children with undefined/null and do nothing
// Consider that children is an array
// Consider that children is an Object, then it has only one son node
if (typeof props.children === 'object' && props.children.type) {
// Single element
render(props.children, dom);
} else if (Array.isArray(props.children)) {
// Multiple elementsreconcileRender(props.children, dom); }}// Record the mounted node
vDom.__dom = dom;
return dom;
}
// Mount the class component
function mountClassComponent(vDom) {
const { type, props } = vDom;
return createDom(new type(props).render());
}
// Mount functional components
function mountFunctionComponent(vDom) {
const { type, props } = vDom;
// type(props) executes FunctionComponent to return the corresponding virtual DOM object
// The createDom method is used to recursively convert the virtual DOM to the real DOM
return createDom(type(props));
}
// Mount multiple DOM elements react. createElement regardless of recursion
function reconcileRender(vLists, parentDom) {
for (let node ofvLists) { render(node, parentDom); }}/** * insert the virtual DOM into the real DOM@param {HTMLElement} Dom elements *@param {Object} The props of the oldProps element itself is used for updates@param {Object} NewProps element newProps */
function updateProps(dom, oldProps, newProps) {
// merge props for now no old, just new
Object.keys(newProps).forEach((key) = > {
if (key === 'children') {
// Handle children alone
return;
}
if (key === 'style') {
addStyleToElement(dom, newProps[key]);
} else if (key === 'content') {
// The text does nothing
} else{ dom[key] = newProps[key]; }}); }function addStyleToElement(dom, styleObject) {
Object.keys(styleObject).forEach((key) = > {
const value = styleObject[key];
dom.style[key] = value;
});
}
const ReactDOM = {
render,
};
export default ReactDOM;
Copy the code
The mounting implementation of a Class component is essentially similar to the implementation of a FunctionComponet component.
- The incoming is judged first
type
Is it a function? If it is a function there are two types. - And then check whether
class
Component, because we already gave the parent classprototype
On the mountisReactComponent
Methods. So throughSubclasses. Prototype. IsReactComponent
To find out ifclass
Components. - If it is
class
Component, then what we need to do is also to put hisrender
Method returnVdom
Object throughcreateDom
Method into realityDom
Node to mount. - Once you’ve got the core of what you need to do, it’s pretty simple
createDom(new type(props).render())
The torender
Method returnvDom
Object, throughcreateDom
To convert the virtual DOM to the real worldDom
.
Here we have a simple class component mounted. In fact, thousands of times from its ancestor. Essentially, we need to use createDom to convert the vDom object passed in to the real DOM.
core idea
createDom
If an ordinary node is passed in, then the corresponding node is used directlytype
Create a label.createDom
If a function component is passed in, the function component is called to get what it returnsvDom
Nodes, and then throughcreateDom
willvDom
Render the actual node.createDom
If you pass in oneclass
Component, thennew Class(props).render()
Get the returnedvDom
Object, which will then be returnedvDom
Render to realityDom
.
Both FC and CC components are essentially based on the encapsulation of ordinary DOM nodes, so we just need to recursively call them directly back to the basic DOM node and then mount it
Write in the last
So far we have completed the basic FunctionComponent, classComponent rendering.
So far we have seen the basic rendering process, which is to recursively convert a vDom object into a real DOM using the createDom method.
After that, we will have a deeper understanding of ClassComponet and Functional Compont, and experience the design philosophy of React.