Bring your own React

React is a a great UI library from Facebook that works well for creating fast browser based user interfaces. Working with it is quite easy. Once you learn the basics you can be quite productive. But to create great applications it is helpful to understand some of the internals of React. But that is where things become a bit more complex. The React source code is not easy to understand. There are a lot of performance optimizations that make code harder to read. Also there are  a lot of browser related issues. Small differences that add a lot more complexity. Another reason is that React is not just for browser based applications and their DOM. It’s also for other platforms like React Native.

When going through the original source code is really hard. Like it is with React. But understanding the choices is beneficial, there is a good alternative. That alternative is to create a simplified own implementation. The goal is not to create a new UI library. No there are other simpler alternatives out there like Preact. The goal is just a teaching tool to better understand React.

 

To JSX or not to JSX?

Using JSX to write React code is not necessary or required. But it is the de-facto standard way of writing React. It makes code a  lot easier to read compared to the plain JavaScript style. Fortunately  JSX is just a format that can be used with different UI libraries.  Transpiling JSX into JavaScript isn’t even done by the React team these days. Instead that is left up to Babel which is pretty much the de-facto standard for transpiling ECMAScript 2015 and JSX.

The way to do this is in fact quite simple. There is a Babel plugin called transform-react-jsx. This is the normal way to transpile JSX code. By default it turns JSX markup elements into React.createElement() functions. Yet by specifying a pragma option you can make it output anything you want. In this case I am going to replace React.createElement() with my own ByoReact.createElement() using the following .babelrc file.

{
"presets": ["es2015", "stage-0", "react"],
"plugins": [
["transform-react-jsx", {
"pragma": "ByoReact.createElement"
}]
]
}

This will allow me to use any new ECMAScript feature and transpile JSX code to my own library.

 

The Hello World of Bring Your Own React

Most development starts with Hello World and there is no reason why not to start there. The fist version of the code is just going to render the following:

image

Not impressive but we have to start  somewhere Smile

The code to render this Hello World is as follows:

import ByoReactDOM from '../../src/bring-your-own-react-dom';
import ByoReact from '../../src/bring-your-own-react'; // eslint-disable-line no-unused-vars

class HelloWorld extends ByoReact.Component {
render() {
return <div>Hello world</div>;
}
}

ByoReactDOM.render(<HelloWorld />,
document.getElementById('app'));

Doing the minimal required means mostly just implementing the ByoReact.createElement(). React itself uses a virtual DOM but in this case I am just going to stick with the real browser DOM. This will change soon enough but this is a nice start. The function is passed three parameters: The tag to render which can be a HTML tag name or a child component. The second is the properties, something we will ignore for now. The last is the list of child components. These child components can either be a string literal or another component.

The code for this is quite simple and always returns a HTML element:

const createElement = (tag, props, ...childeren) => {
let result;
if (typeof tag === 'string') {
result = document.createElement(tag);
} else {
const component = new tag(); // eslint-disable-line new-cap
result = component.render();
}

for (const child of childeren) {
if (typeof child === 'string') {
const textNode = document.createTextNode(child);
result.appendChild(textNode);
} else {
result.appendChild(child);
}
}

return result;
};

The base class Component is there but as it doesn’t contain any functionality yet there is not much to see. Again this will change as we get further along.

This leaves rending the <HelloWorld /> component in the browser using ByoReactDOM.render(). Again there is little to this yet as the ByoReact.createElement() returns a DOM object.

const render = (reactElement, domContainerNode) => {
domContainerNode.innerHTML = reactElement.outerHTML; // eslint-disable-line no-param-reassign
};
 
When we switch to a virtual DOM and updating existing UI components this will become a lot more complex. This will basically trigger the reconciliation process. This is the complex logic to determine the difference between the previous and next DOM and apply it as efficient as possible.
 
You can browse the complete source code, including unit tests, here.
 
Enjoy Smile

Leave a Reply

Your email address will not be published. Required fields are marked *