preface
Today we will not share React specific grammar, components, communication, Ref, Portals, Context, Hoc, Hook and other knowledge points, these things, you can be familiar with the development task by carefully looking at the official documents, but today I want to talk about React working principle.
If you want to know the basic knowledge of React, please check my demo, nearly 100 Start React Antd Shop
Lets Go !
What is the virtual Dom? What is Jsx? How does React work? . .
Let's conduct a shallow analysis of React according to the above three questions!
What is the virtual Dom?
Virtual DOM (Virtual DOM) is a programming concept in which the UI is stored in memory as a Virtual structure and then converted into a real DOM through compilation. So what is it?
The object, yes, is a Javascript object that represents the Dom information and structure. When the state changes, the object structure is rerendered. This Javascript object is called a Virtual Dom.
Why not just manipulate the DOM? Simply because THE DOM is slow to operate, even small changes can cause pages to be rearranged and redrawn, which costs performance. Therefore, the DIff algorithm can be used to operate Js objects in batches and minimize Dom operations, so as to improve performance. Diff we post analysis.
What is Jsx?
Jsx is a syntax extension for javascript + XML, but any {} will be executed as javascript, otherwise it will be XML.
Why do YOU need Jsx? Maybe it’s because Jsx templates are concise and syntax is flexible, which I don’t know.
How babel-Loader precompiles Jsx to React. CreateElement (…) Function execution.
So how does React work?
Start with Jsx, first let’s look at a piece of code on the official website
A regular React component
class HelloMessage extends React.Component {
render() {
return (
<div className="HelloClass" num="1">
Hello {this.props.name}
<span> is span</span>
</div>
);
}
}
ReactDOM.render(
<HelloMessage name="Taylor" />.document.getElementById('hello-example'));Copy the code
This regular React component is babel-Loader.
class HelloMessage extends React.Component {
render() {
return React.createElement(
"div"./ / type
{ className: "HelloClass".num: "1" }, / / property
"Hello "./ / content
this.props.name, // props
React.createElement( // Nesting - same thing
"span".null." is span")); }}// Class components are also converted to 'React. CreateElement (...) 'function execution
ReactDOM.render(React.createElement(HelloMessage, { name: "Taylor" }),
document.getElementById('hello-example'));
Copy the code
After a simple analysis of the code, did you find that the principle of Jsx we mentioned above is verified? After babel-Loader compilation, it becomes React.createElement(…). Function, if nested, then React. CreateElement (…) Also the nested
Class HelloMessage extends React.Com props class HelloMessage extends React.Com props Class HelloMessage extends React.Com props
okey ! At this point, we can have a simple conclusion, which is:
- The React component inherits from the React.ponent class
class HelloMessage extends React.Component
- Jsx syntax compiled by Babel-Loader becomes
React.createElement(...) Function performs
, and layers are nested - The element is mounted on the page through the reactdom.render method
Let’s analyze it in turn and simply implement it:
React.component
export default function Component (props) {
// Contains the props parameter
this.props = props;
}
// Define an identity for a class component and a function component. The source code is identified as such, although for some reason it is not written as a Boolean
Component.prototype.isReactComponent = {};
Copy the code
React.createElement(…)
Back to the React. CreateElement method (…). , then the React. CreateElement method (…). How many parameters do you receive? From the above example, it can be concluded:
- Parameter 1: type (element type rendered, component type, etc.)
- Parameter 2: attributes (attributes of an element or component, including className, ID, custom props, etc.)
- Parameter 3: content of the child node
There may be many children
Okey, with all that said, let’s hand write a mini React. CreateElement ().
When writing the React. CreateElement method (…). What parameter is passed in, what value is returned, and what parameter is passed in. What parameter is passed in
So what does that return? Let’s look at the createElement source code:
/ / position: the react/packages/react/SRC/ReactElement js 348 rows
// Delete the previous processing, easy to read
export function createElement(type, config, children) {
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
Copy the code
We need to return a ReactElement object containing type, key, ref, self, source, props, etc. We want to know how react works. Let’s drop key, ref, self for a moment, and let’s go ahead and do the code
/* Return props type (props type); /* return props type (props type); "Border "," name ":" function component ", __source: {... }, __self: undefined} children: undefined */
function createElement (type, config, ... children) {
// remove __source: {... }, __self: undefined property, easy console view
if (config) {
delete config.__source;
delete config.__self;
}
// Key.ref.slef is not considered here
constprops = { ... config,/* createTextNode specifies the data structure of the text node. If the child node is an object, the child node has a child node. {type:" type ", props:{}}. Babel -Loader compiles JSX to React. CreteElement nested */
children: children.map(child= >
typeof child === "object" ? child : createTextNode(child)
)
};
console.log({type,props});
return {
type,
props
};
}
// Select "TEXT" from "TEXT"; // Select "TEXT" from "TEXT";
function createTextNode (text) {
return {
type: "TEXT".props: {
children: [].nodeValue: text
}
};
}
Copy the code
Let’s take a look at the final return, my own sample
okey ! A mini version of the React. CreateElement () method has been implemented. Let’s go to the render method
ReactDOM. Render (…)
The render method in the react-dom module seems to pass two parameters, but there are actually three parameters, and the third one is not used very often. Container to place after parsing 3. Callback function
/ / position: the react/packages/react - dom/SRC/client/ReactDOMLegacy js 287 rows
export function render(
element: React$Element<any>, / / the react elements
container: Container, // The container to be placed
callback: ?Function.// The callback function
) {
/ /... A lot of logic
/ / return calls legacyRenderSubtreeIntoContainer methods here
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
Copy the code
LegacyRenderSubtreeIntoContainer ultimately go down
/ / position: the react/packages/react - dom/SRC/client/ReactDOMLegacy js 175 rows
function legacyRenderSubtreeIntoContainer(parentComponent: ? React$Component<any, any>, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function.) {
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
// Determine if the component is rendered for the first time. If so, create it and update the DOM, otherwise diff the DOM directly
if(! root) { root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot;if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
/ / update the dom
unbatchedUpdates(() = > {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
/ / update the dom
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
Copy the code
There is still a simple idea in mind, and that is
- 1,
render
The react method takes three arguments, the react element, the container to place after parsing, and the callback function - 2, follow the call
legacyRenderSubtreeIntoContainer
Methods, create the dom | | to update the dom, edge involves two key here, tooReact
Core knowledge pointsFiber data structure, DIFFF algorithm
We’ll find out later
Let’s implement the render method in a simple way:
Create a render function that does two things (aside from the callback function)
/* Vnode JSX {vnode -> node} vnode JSX {vnode -> node} container.appendChild(node); To mount the real DOM to container */
function render (vnode, container) {
const node = createNode(vnode, container);
node && container.appendChild(node);
}
Copy the code
Implement a createNode to convert the virtual DOM to the real DOM
/* the virtual dom is rendered as the real dom. Vnode the parentNode of the virtual dom parentNode, that is, the corresponding parent container to which the virtual dom is mounted */
function createNode (vnode, parentNode) {
// Transform the final real DOM node of the combination
let node = null;
/* props: props and children */
const { type, props } = vnode;
// Todo generates DOM nodes based on the node type
if (type === "TEXT") {
// TEXT node, type = "TEXT", nodeValue = content value of the TEXT node
node = document.createTextNode("");
} else if (typeof type === "string") {
// Create a native tag node with a string type such as div, p, span, etc
node = document.createElement(type);
} else if (typeof type === "function") {
// Class component and function component, type is function
// isReactComponent custom identifier, defined on the Component prototype
node = type.prototype.isReactComponent
? updateClassComponent(vnode, parentNode) // The class component converts dom methods
: updateFunctionComponent(vnode, parentNode); // The function component converts the DOM method
}
/* render (props. Children); /* render (props. Children); Child nodes are like class components, function components, and we put them outside */
reconcileChildren(node, props.children);
/* Update dom node: props: props and child node properties and child node content values
updateNode(node, props);
return node;
}
Copy the code
Now we have a simple render method logic in front of us, all that remains is to implement the corresponding transformation logic step by step, let’s do one by one:
Class components
Let’s mock a class component that inherits from our custom react.ponent class.
class ClassComponent extends Component {
render () {
return (
<div className="border">
ClassComponent - {this.props.name}
</div>); }}Copy the code
Parse a class component by instantiating it and then executing Render to return the corresponding virtual DOM and then converting it through the createNode logic above
function updateClassComponent (vnode, parentNode) {
// Get the virtual DOM type and properties
const { type, props } = vnode;
// instantiate, because the class component type is a function, pass the attribute props to it
const instance = new type(props);
// Execute the render function of the class component to get the virtual DOM
const vvnode = instance.render();
// Convert the virtual DOM to the real DOM according to the conversion logic
const node = createNode(vvnode, parentNode);
// Return the real DOM
return node;
}
Copy the code
Function component
function FunctionComponent (props) {
return <div className="border">FunctionComponent-{props.name}</div>;
}
Copy the code
A function component is simpler than a class component, executing the function directly, returning the corresponding virtual DOM and translating it through the createNode logic
function updateFunctionComponent (vnode, parentNode) {
const { type, props } = vnode;
const vvnode = type(props);
const node = createNode(vvnode, parentNode);
return node;
}
Copy the code
Child nodes
Iterate over the child nodes, recursively executing render to go to the createNode conversion logic;
function reconcileChildren (node, children) {
for (let i = 0; i < children.length; i++) {
constchild = children[i]; render(child, node); }}Copy the code
Update the dom
To update the DOM node, nextVal => props and child node properties and their values are then mounted to the real DOM node
function updateNode (node, nextVal) {
Object.keys(nextVal)
.filter(k= >k ! = ="children")
.forEach(k= > {
// console.log(node[k] + "----" + nextVal[k]);
node[k] = nextVal[k];
});
}
Copy the code
Down ~
Going back to our official example, isn’t a simple principle already clear
class HelloMessage extends React.Component {
render() {
return (
<div>
Hello {this.props.name}
</div>
);
}
}
ReactDOM.render(
<HelloMessage name="Taylor" />.document.getElementById('hello-example'));Copy the code
The mini – react – demo entrance
Follow-up: Let’s study fiber data structure and DIFF algorithm again.
Welcome to like, a little encouragement, a lot of growth