Suits the crowd
This article is suitable for advanced development of 0.5~3 years react developers.
Talk nonsense:
React source code, it is indeed more difficult than vUE deeper, this article is also aimed at beginners, intended to let bloggers understand the whole react implementation process.
Last article, from the mini source code analysis vUE, perhaps the depth of the general, but also spent a few days to summary, understand the difficulty of a blogger. This is my first post to get hundreds of likes, good reviews, and dozens of temporary followers. That’s what drives my blog.
Under the very busy condition this week, I still found time to summarize React. If you think it’s good, give it a “like” (three steps is my favorite)
How to also need to learn vUE source code and vUE related knowledge points of friends, suggested to move: juejin.cn/post/684790…
In addition, to synchronize the progress of the blogger source series:
| | | blog theme sequence number link | | — – | — – | — — — — — — — — — — — – | | | 1 handwritten vue_mini source code parsing | juejin. Cn/post / 684790… | | 2 | handwritten react_mini source code parsing (that is, in this article) | juejin. Cn/post / 685457… | | 3 | handwritten webpack_mini source code parsing | juejin. Cn/post / 685457… | | | 4 handwritten jquery_mini source code parsing | juejin. Cn/post / 685457… | | | 5 handwritten vuex_mini source code parsing | juejin. Cn/post / 685705… Analytical | | | | 6 handwritten vue_router source is expected in August | | | 7 handwritten diff algorithm source code parsing | is expected in August | | | 8 handwritten promis source code parsing | | August is expected to | | 9 handwritten native js source code parsing (manual to realize common API) | Is expected in August | | | handwritten react_redux 10, fiberd | backlog such as source code parsing, this plan out in this paper, first, arrange some difficulty | | | 11 handwritten koa2_mini | is expected in September, the front-end | priority
Essential knowledge before writing source code
JSX
First we need to understand what JSX is.
React uses JSX instead of regular JavaScript. JSX is a JavaScript syntax extension that looks a lot like XML.
Yes, JSX is a syntax extension of JS, ostensibly like HTML, but essentially executed by Babel conversion to JS. In a more general way, JSX is just a piece of JS written as HTML, and when we read it, JSX will automatically convert it into a VNode object. This is done with the help of the built-in Babel in React-Script.
Here’s a simple example:
Return (<div> Hello Word </div>) return react.createElement ("div", null, "Hello")Copy the code
JSX essentially builds a virtual Dom inside React by converting it to React. CreateElement and finally renders the page.
Virtual Dom
Here’s a look at react’s virtual DOM. React’s virtual DOM is very different from Vue’s. Vue’s virtual DOM is to improve rendering efficiency, while React’s virtual DOM is definitely needed. It makes sense that vue’s template itself is HTML and can be displayed directly. JSX is JS and needs to be converted to HTML, so virtual DOM is used.
Let’s describe the react vnode in its simplest form:
function createElement(type, props, ... children) { props.children = children; return { type, props, children, }; }Copy the code
Div,span, props, {id: 1, style:{color:red}}, children, children.
Introduction of the principle
Let’s write the simplest source code for React:
Function (props){return <div> </div> </div>} ReactDOM.render(<App/>, document.getElementById('root'))Copy the code
- React is responsible for logic control, data -> VDOM
First, we can see that every JS file must import React from ‘React’. But in our code, we don’t use React at all. But if you don’t introduce him, it’s a mistake.
Why is that? It can be understood that in our JS file above, we use JSX. JSX does not compile, so an error is reported. React is used to convert JSX into a “virtual DOM” object.
JSX essentially builds a virtual Dom inside React by converting it to React. CreateElement and finally renders the page. And we introduced React for the purpose of this process.
- ReactDom renders the actual DOM, VDOM -> DOM
So with that in mind, let’s look at ReactDOM. React converts JSX into a “virtual DOM” object. We use ReactDom virtual DOM through the render function, converted into a DOM. By inserting it into our real page.
This is the whole Mini React process in brief.
Handwriting react process
1) Build the basic shelf
The functionalization of React, we’ll leave that out of the question. For example, how to start React, how to recognize JSX, how to implement hot update service, etc., our focus is on React itself. Let’s borrow the React-scripts plugin.
There are several ways to create our basic shelf:
-
Use create-react-app zwz_react_origin to quickly build, and then delete the original react,react-dom and other files. (Zwz_react_origin is my project name)
-
Second, copy the code below. The new package. The json
{"name": "zwz_react_origin", "scripts": {"start": "react-scripts start"}, "version": "0.1.0", "private": True, "dependencies" : {" react - scripts ":" 3.4.1 track "},}Copy the code
Then create index. HTML under public
<! DOCTYPE html> <html lang="en"> <head> </head> <body> <div id="root"></div> </body> </html>Copy the code
Create index.js under SRC
In this case, react-scripts will quickly help us to set index. HTML and introduce index.js
import React from "react"; import ReactDOM from "react-dom"; Let JSX = (<div> <div className="">react </div> </div>); ReactDOM.render(jsx, document.getElementById("root"));Copy the code
That’s a wheel for writing react source code.
-
Third, if you are still lazy, directly guide the author’s project down.
Link: github.com/zhuangweizh…
2) React source code
Let obj = (<div> <div className="class_0">); console.log(`obj=${ JSON.stringify( obj) }`);Copy the code
First, our code above, if we don’t import React processing, we can print: ‘React’ must be in scope when using JSX React /react-in-jsx-scope The JSX does not parse into the virtual DOM, and our page will report an error. By looking at the data, or tracing the source code, we can see that, in fact, after JSX is recognized, the createElement in the page is called to convert to the virtual DOM.
Let’s import React and see what it prints out. Okay?
+ import React from "react"; Let obj = (<div> <div className="class_0">); console.log(`obj:${ JSON.stringify( obj) }`); Results: jsx={"type":"div","key":null,"ref":null,"props":{"children":{"type":"div","key":null,"ref":null,"props":{"className":"c Lass_0 ", "children" : "hello"}, "_owner _store", null, "" : {}}}," _owner: null, "" _store" : {}}Copy the code
As you can see from the above conclusion, Babel recognizes our JSX by creating the Element and converting its DOM (HTML syntax) into a virtual DOM. From the above procedure, we can see that the virtual DOM consists of type,key,ref,props. Let’s simulate the react source code.
Now that we know what the createElement in react does, we can try to write a createElement ourselves:
function createElement() {
console.log("createElement", arguments);
}
export default {
createElement,
};
Copy the code
The print result is as follows:
We can see that when we pass the object in the DOM, we pass in type, props, and so on.
function createElement(type, props, ... children) { props.children = children; return { type, props, }; }Copy the code
So, we have implemented the simplest version of a React, let’s see how to render to the page
3) ReactDom.render
import React from "react"; + import ReactDOM from "react-dom"; Let JSX = (<div> <div className="class_0"> hello </div> </div>); // console.log(`jsx=${ JSON.stringify( jsx) }`); + ReactDOM.render(jsx, document.getElementById("root"));Copy the code
If, at this point, we introduce ReactDom and render it to the corresponding element, the whole react lite version will be complete and the page will be rendered. First, we already know that JSX is a VNode, and the second element is the element of the rendered page, assuming that our element is an HTML native tag div. Let’s create a new reactdom.js to introduce.
function render(vnode, container) { mount(vnode, container); } function mount(vnode, container){ const { type, props } = vnode; const node = document.createElement(type); // Create a real dom const {children,... rest } = props; If (array.isarray (item)) {item.map(c => {mount(c, node); }); } else { mount(item, node); }}); container.appendChild(node); } // home: -import React from "React "; - import ReactDOM from "react-dom"; + import React from "./myReact/index.js"; + import ReactDOM from "./myReact/reactDom.js"; Let JSX = (<div> <div className="class_0"> hello </div> </div>); ReactDOM.render(jsx, document.getElementById("root"));Copy the code
At this point, we can see that the page, a react rendering that we wrote ourselves, has been completed. Let’s optimize.
First of all, in this process, className=”class_0″ disappears. We tried to render the page. At this point, there is no way for objects in the virtual DOM to distinguish which elements have which attributes, so we will optimize the mount when escaping.
function mount(vnode, container){ const { type, props } = vnode; const node = document.createElement(type); // Create a real dom const {children,... rest } = props; If (array.isarray (item)) {item.map(c => {mount(c, node); }); } else { mount(item, node); }}); Object.keys(rest).map(item => {if (item === "className") {node.setAttribute("class", rest[item]); } if (item.slice(0, 2) === "on") { node.addEventListener("click", rest[item]); }}); // + end container.appendChild(node); }Copy the code
4) ReactDom.Component
See here, the entire string render to page process is complete. At this point the entry file is resolved. For the original div tag, h1 is already compatible. But what about custom tags? Or how to do componentization.
Let’s first look at the two componentization modes of React16 +, one is function componentization, the other is class componentization.
First of all, let’s take a look at the demo.
import React, { Component } from "react"; import ReactDOM from "react-dom"; class MyClassCmp extends React.Component { constructor(props) { super(props); } render() {return (<div className="class_2" >MyClassCmp = {this.props. Name}</div>); }} function MyFuncCmp(props) {return <div className="class_1" >MyFuncCmp = {props. } let JSX = (<div> <h1> hello </h1> <div className="class_0"> <div> <MyFuncCmp /> <MyClassCmp /> </div>); ReactDOM.render(jsx, document.getElementById("root"));Copy the code
Let’s start with the simpler Function component. The difference between Function and JSX is that it is a Function, whereas JSX is a string. We can use this feature to convert a Function to a string, so that the Function component has the same properties as a normal tag.
Let’s write a method:
mountFunc(vnode, container);
function mountFunc(vnode, container) {
const { type, props } = vnode;
const node = new type(props);
mount(node, container);
}
Copy the code
In this case, the type is the content of the function body. We only need to instantiate it to get the corresponding string, which is an ordinary vnode. Use our original VNode conversion method to achieve.
The way to think about it, if we don’t think about the life cycle and things like that that are relatively complicated. We are also relatively simple, just get the render function in the class.
mountFunc(vnode, container);
function mountClass(vnode, container) {
const { type, props } = vnode;
const node = new type(props);
mount(node.render(), container);
}
Copy the code
You might notice here that the class component, you need to inherit from React.ponent. Take a screenshot of the React Component
As you can see, Component encapsulates setState, forceUpdate methods, props, state,refs, and so on. Let’s simulate a simple version of chestnuts:
class Component {
static isReactComponent = true;
constructor(props) {
this.props = props;
this.state = {};
}
setState = () => {};
}
Copy the code
Add another identifier, isReactComponent, to indicate that the number of functions is componentized. In this way, we can distinguish between normal tags, function component tags, and class component tags.
We can refactor the createElement method to define an additional vType attribute for each
-
- Common label
-
- Function component label
-
- Class component label
According to the above marks, we can be transformed into:
function createElement(type, props, ... children) { props.children = children; let vtype; if (typeof type === "string") { vtype = 1; } if (typeof type === "function") { vtype = type.isReactComponent ? 2:3; } return { vtype, type, props, };Copy the code
So, when we deal with:
function mount(vnode, container) {
const { vtype } = vnode;
if (vtype === 1) {
mountHtml(vnode, container); //处理原生标签
}
if (vtype === 2) {
//处理class组件
mountClass(vnode, container);
}
if (vtype === 3) {
//处理函数组件
mountFunc(vnode, container);
}
}
Copy the code
At this point, we have completed a simple componentized React source code. However, there is a bug in this case, which is that text elements are not tagged. Let’s optimize.
function mount(vnode, container) { const { vtype } = vnode; if (! vtype) { mountTextNode(vnode, container); // Processing text nodes} //vtype === 1 //vtype === 2 //.... Function mountTextNode(vnode, container) {const node = document.createTextNode(vnode); container.appendChild(node); }Copy the code
Simple source code:
Package. Json:
{" name ":" zwz_react_origin ", "version" : "0.1.0 from", "private" : true, "dependencies" : {" react ":" ^ 16.10.2 ", "the react - dom" : "^ 16.10.2 react -", "scripts" : "3.2.0"}, "scripts" : {" start ":" the react - scripts start ", "build" : "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": }, "browserslist": {production": [">0.2%", "not dead", "not op_mini all"], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }Copy the code
index.js
import React from "./wzReact/"; import ReactDOM from "./wzReact/ReactDOM"; class MyClassCmp extends React.Component { constructor(props) { super(props); } render() {return (<div className="class_2" >MyClassCmp = {this.props. Name}</div>); }} function MyFuncCmp(props) {return <div className="class_1" >MyFuncCmp = {props. } let JSX = (<div> <h1> hello </h1> <div className="class_0"> <div className="class_0"> </div> <MyFuncCmp name=" "/> <MyClassCmp name=" "/> </div> ); ReactDOM.render(jsx, document.getElementById("root"));Copy the code
/wzReact/index.js
function createElement(type, props, ... children) { console.log("createElement", arguments); props.children = children; let vtype; if (typeof type === "string") { vtype = 1; } if (typeof type === "function") { vtype = type.isReactComponent ? 2:3; } return { vtype, type, props, }; } class Component { static isReactComponent = true; constructor(props) { this.props = props; this.state = {}; } setState = () => {}; } export default { Component, createElement, };Copy the code
/wzReact/ReactDOM.js
function render(vnode, container) { console.log("render", vnode); //vnode-> node mount(vnode, container); // container.appendChild(node) } // vnode-> node function mount(vnode, container) { const { vtype } = vnode; if (! vtype) { mountTextNode(vnode, container); } if (vtype === 1) {mountHtml(vnode, container); } if (vtype === 3) {// Handle mountFunc(vnode, container); } if (vtype === 2) {// mountClass(vnode, Container); Function mountTextNode(vnode, container) {const node = document.createTextNode(vnode); container.appendChild(node); Function mountHtml(vnode, container) {const {type, props} = vnode; const node = document.createElement(type); const { children, ... rest } = props; children.map(item => { if (Array.isArray(item)) { item.map(c => { mount(c, node); }); } else { mount(item, node); }}); Object.keys(rest).map(item => { if (item === "className") { node.setAttribute("class", rest[item]); } if (item.slice(0, 2) === "on") { node.addEventListener("click", rest[item]); }}); container.appendChild(node); } function mountFunc(vnode, container) { const { type, props } = vnode; const node = new type(props); mount(node, container); } function mountClass(vnode, container) { const { type, props } = vnode; const cmp = new type(props); const node = cmp.render(); mount(node, container); } export default { render, };Copy the code
This concludes the source code for the Mini simple version of this article, which will be sent at the end of the article. The react family barrel is not involved because of the positioning of this article. The next article, fiber, redux, hooks and other concepts or source analysis, will be summarized in the new article. If it works for you, stay tuned for future articles.