Build your own React.js - Part 1. ReactDOM.render

rinatrezyapov

Rinat Rezyapov

Posted on January 20, 2021

Build your own React.js - Part 1. ReactDOM.render

Introduction

These series of articles are based on the Paul O Shannessy - Building React From Scratch talk that he gave in 2016.

He also uploaded source code to the GitHub repo.

Unfortunately, the talk was only 30 minutes long and to fully understand each part of this simplified implementation of React.js I spent days reading the source code and the comments. Even though the comments and the talk were super helpful, some parts of the code were like rocket science to me, especially recursive labyrinths of madness, until I was enlightened.

And now I decided to create a series of articles based on my notes and 'aha' moments.

I also recently found out that React.js team added their annotations to Building React From Scratch talk. And this document is pretty good (I hope it was available when I first started deep dive in it) but I still decided to create my own, more detailed version, maybe someone will find it easier to understand.

Main points to consider before starting to read these articles:

  1. This is simplified React.js implementation, meaning that some parts of React.js are not included, such as refs, function components, keys, rendering arrays e.t.c;
  2. This is the implementation of pre-fiber React.js. That is 15.x.x version;
  3. For simplicity sake, I'm going to change some confusing for me parts of the original implementation and I'm not going to use package dependencies at all. That means, for example, instead of using babel plugin to transform jsx I will describe elements using JavaScript objects;
  4. These articles are written in a very simple way so even beginners in JavaScript and React.js should understand what is going on;
  5. At the end of each article, I will put a link to Codesandbox example and flowchart of what we've covered.
  6. The source code will be available in this repo (by commit for each article);

Someone might ask a question "Why to spend time trying to understand the mechanics of the older version of React.js?"

Because I think you need to know "how it started" to understand "how it's going".

Let's go!

How it starts

Let's start with refreshing our memory about how elements are described in React.js without using jsx.

  // Describing component
  <App /> -> { type: App }
   // Describing component with props
  <App title="React.js" /> -> {type: App, props: {title: "React.js"}}

  // Describing element
  <div></div> -> { type: "div" }
Enter fullscreen mode Exit fullscreen mode

Notice the difference: for class component we use class component itself in the type field, for a DOM element we use string representation of this element. For now, that's all that we need to know.

I also want to clarify that by saying element I mean two things:

  1. Object with the type field pointing to a class component (App) or DOM element ('div');
  2. Simple JavaScript string (remember we can render strings?);

As you may know, typical React.js application starts by calling ReactDOM.render function where we pass an element as the first argument and a node which we use as a mounting point as the second argument.

To simplify things I want to add one restriction: we pass only class component element as the first argument to ReactDOM.render.

ReactDOM.render({ type: App }, document.getElementById("root"))
Enter fullscreen mode Exit fullscreen mode

Let's look at how we would implement this render function.

function render(element, node) {
  if (isRoot(node)) {
    update(element, node);
  } else {
    mount(element, node);
  }
} 
Enter fullscreen mode Exit fullscreen mode

part-1-render

As you can see, render function checks by using isRoot function whether we already mounted App class component to the node with the root id or not. If the class component is already mounted we perform the update, if not mount.

  const ROOT_KEY = "root";

  function isRoot(node) {
    return node.dataset[ROOT_KEY];
  }
Enter fullscreen mode Exit fullscreen mode

isRoot checks if our node has an HTML5 dataset attribute with the name data-root. We set this attribute in the mount function, which we will discuss later.

part-1-mount-dataset

Mount

Since initially we didn't mount anything to the node and didn't set data-root attribute we skip update and call mount function.

  function mount(element, node) {
    node.dataset[ROOT_KEY] = rootID;
    const component = instantiateComponent(element);
    ... 
  }
Enter fullscreen mode Exit fullscreen mode

In the mount, we set data-root attribute of the node to signalise that we are performing mounting.

Then, we instantiate the class component. Let's discuss what it means.

instantiateComponent will be used in several places and several conditions will be added to the body of the function in the future but for now, we just assume that element argument will be an object with the type field pointing to a class component.

  function instantiateComponent(element) {
    const wrapperInstance = new element.type(element.props);
    wrapperInstance._construct(element);

    return wrapperInstance;
  }
Enter fullscreen mode Exit fullscreen mode

Since element.type points to a class component we can use it to create an instance of this class component.

From React.js docs: Class components have instances, but you never need to create a component instance directlyโ€”React takes care of this.

Well, in this case, we are building our own React.js so we have to take care of this :) Calling new element.type(element.props) in the following code snippet is the same as calling class constructor new App(element.props).

  const element = { type: App, props: { title: "React.js" }}
  new element.type(element.props) ---> new App({ title: "React.js" })
Enter fullscreen mode Exit fullscreen mode

After an instance of the class component is created instantiateComponent function calls this strange _construct method of the newly created instance. But what the heck is _construct? Let's answer this question by trying to fire up what we already implemented. Spoiler alert: It will break.

First render

We will create App class component and use a div element with root id in our HTML file as a mounting point just as we do it in a real React.js application. But watch carefully, doesn't something look suspicious to you?

class App {}

render({ type: App }, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

Codesandbox example

That's right! Our App class must extend React.Component class to become a legitimate React.js class component. And _construct method of the instance of App class that we created is actually the method of React.Component class that App inherits when we create it as:

  class App extends React.Component {

  }
Enter fullscreen mode Exit fullscreen mode

Let's call our implementation of React.Component just Component from now.

part-1-extending-component-class

If you are not sure how classes and prototype chain work in JavaScript I recommend watching this video about it.

This is a flowchart of what we've covered so far:

part-1-flowchart
That's it for now. In the next episode of the Build your own React.js series we will implement Component class.

Thanks for reading! If you liked this article and want more content like this, checkout my blog and make sure to follow me on Twitter!

Links:

  1. Github repo with the source code from this article
  2. Codesandbox with the code from this article
  3. Building React From Scratch talk
  4. React.js docs regarding Building React From Scratch talk
  5. HTML5 dataset attribute
  6. Describing elements using JavaScript objects
  7. The Definitive Guide to Object-Oriented JavaScript
๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
rinatrezyapov
Rinat Rezyapov

Posted on January 20, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About