preface

React is one of the most popular frameworks on the front end, and there are a lot of articles about its source code, but I want to read React from another Angle: Implement React from scratch. Implement most functions of React from the API level. In this process, explore why there are virtual DOM, diff, setState and so on.

When it comes to React, there is always a comparison with Vue

Vue’s API design is very simple, but the way it’s implemented feels like “magic”, and developers can get used to it right away, but it’s not clear why it works.

React’s design philosophy is very simple, and while it often has to deal with details on its own, it feels very “real” and clearly feels like you’re still writing JS.

About the JSX

Before we begin, it’s worth clarifying some concepts.

Let’s take a look at this code:

const title = <h1 className="title">Hello, world!</h1>;
Copy the code

This code is not legitimate JS code, it is a syntax extension called JSX, through which we can easily write HTML fragments in JS code.

In essence, JSX is syntactic sugar, and the code above is translated by Babel into the following code

const title = React.createElement(
    'h1',
    { className: 'title' },
    'Hello, world! '
);
Copy the code

You can test the JSX transformed code online at Babel, and here is a slightly more complex example

The preparatory work

To focus on writing your logic, you have chosen parcel, the recently popular zero-configuration packaging tool, on your code packaging tool. You need to install Parcel first:

npm install -g parcel-bundler
Copy the code

Next, create index.js and index.html, and add index.js to index.html.

Of course, there is an easier way, you can download the repository code directly:

https://github.com/hujiulong/simple-react/tree/chapter-1

Note the configuration of Babel. Babelrc

{
    "presets": ["env"]."plugins": [["transform-react-jsx", {
            "pragma": "React.createElement"}}]]Copy the code

Transform-react-jsx is the Babel plugin that converts JSX to JS. There is a pragma that defines the name of the JSX conversion method. You can change it to H (the name used by many react like frameworks) or whatever.

Once we’re ready, we can run it with the command parcel index.html, which of course doesn’t have anything yet.

React. CreateElement and the virtual DOM

As mentioned earlier, JSX fragments are translated into code wrapped in the react.createElement method. So the first step is to implement the React. CreateElement method

From the JSX translation, the createElement method takes the following parameters:

createElement( tag, attrs, child1, child2, child3 );
Copy the code

The first argument is the tag name of the DOM node, and its value could be div, H1, SPAN, etc. The second argument is an object that contains all the attributes, maybe className, ID, etc. And then the third argument is its child node

Our implementation of createElement is very simple; all we need to do is return an object to hold its information.

function createElement(tag, attrs, ... children) {
    return {
        tag,
        attrs,
        children
    }
}
Copy the code

Arguments to the function… Children uses the REST parameter of ES6, which merges the following child1,child2, and other parameters into an array of children.

Now let’s try calling it

// Place the createElement method defined above in the React object
const React = {
    createElement
}

const element = (
    <div>
        hello<span>world!</span>
    </div>
);
console.log( element );
Copy the code

When we open the debugging tool, we can see that the output objects are as expected

Our createElement method returns an object that holds all the information about the DOM node. In other words, it generates the real DOM, which we call the virtual DOM.

ReactDOM.render

Next up is the reactdom.render method, so let’s look at this code again

ReactDOM.render(
    <h1>Hello, world!</h1>.document.getElementById('root'));Copy the code

After the transformation, the code looks like this

ReactDOM.render(
    React.createElement( 'h1'.null.'Hello, world! ' ),
    document.getElementById('root'));Copy the code

So the first argument to Render actually takes the object returned by createElement, which is the virtual DOM, and the second argument is the mounted target DOM

To summarize, the render method is used to render the virtual DOM into the real DOM. Here’s how it works:

function render( vnode, container ) {
    
    // When vNode is a string, the render result is a text
    if ( typeof vnode === 'string' ) {
        const textNode = document.createTextNode( vnode );
        return container.appendChild( textNode );
    }

    const dom = document.createElement( vnode.tag );

    if ( vnode.attrs ) {
        Object.keys( vnode.attrs ).forEach( key= > {
            if ( key === 'className' ) key = 'class';            // When the property name is className, change it back to class
            dom.setAttribute( key, vnode.attrs[ key ] )
        } );
    }

    vnode.children.forEach( child= > render( child, dom ) );    // Recursively render child nodes

    return container.appendChild( dom );    // Mount the render results to the real DOM
}
Copy the code

React changed the class name to className to avoid collisions between the class name and the js keyword class. When rendering to the DOM, you need to change it back.

There is actually a minor problem here: when the Render function is called multiple times, the original content is not cleared. So when we attach it to the ReactDOM object, we first clear the contents of the mounted target DOM:

const ReactDOM = {
    render: ( vnode, container ) = > {
        container.innerHTML = ' ';
        returnrender( vnode, container ); }}Copy the code

Render and update

Now that we’ve implemented the basics of React, we’re ready to do something with it.

Let’s start by adding a root node to index.html

<div id="root"></div>
Copy the code

Let’s try Hello,World from the official documentation

ReactDOM.render(
    <h1>Hello, world!</h1>.document.getElementById('root'));Copy the code

You can see the result:

Try rendering a piece of dynamic code. This example also comes from the official documentation

function tick() {
    const element = (
        <div>
            <h1>Hello, world!</h1>
            <h2>It is {new Date().toLocaleTimeString()}.</h2>
        </div>
      );
    ReactDOM.render(
        element,
        document.getElementById( 'root')); } setInterval( tick,1000 );
Copy the code

You can see the result:

The latter

In this article, we implemented the very basics of React and learned about JSX and the virtual DOM. In the next article, we’ll implement some very important component features.

Last but not least, we have to import the React object, whether it is used or not. Why?

Such as:

import React from 'react';    // This code does not use the React object. Why import it
import ReactDOM from 'react-dom';

ReactDOM.render( <App />, document.getElementById( 'editor' ) );
Copy the code

If you don’t know the answer, take a closer look at this article

Implement the React series from scratch

React is one of the most popular frameworks on the front end, and there are a lot of articles about its source code, but I want to read React from another Angle: Implement React from scratch. Implement most functions of React from the API level. In this process, explore why there are virtual DOM, diff, setState and so on.

There will be about six articles in this series, and I will update one or two articles every week. I will update them on Github as soon as possible. If you have any questions, please reply to me on Github

Blog address: github.com/hujiulong/b… Focus on STAR and subscribe to Watch