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

    1. Common label
    1. Function component label
    1. 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.