Gooact: React in 160 lines of JavaScript
Build your own React clone in a matter of minutes.
Introduction
Important Notice: Following tutorial requires at least basic React knowledge.
React is a great library — many developers instantly fell in love with it due to simplicity, performance and declarative way of doing things. But personally I have a specific reason what makes it so special for me — and that’s how it works under the hood. I find ideas that stand behind React simple yet strangely fascinating — and I believe that understanding its core principles would help you writing faster and safer code.
In this tutorial I am going to show you how to write a fully functional React clone, including Component API and own Virtual DOM implementation. It is divided into four sections — each describing one major topic:
- Elements: In this section we are going to learn how JSX blocks are processed into lightweight version of DOM called VDOM.
- Rendering: In this section I will show you how to transform VDOM into regular DOM.
- Patching: In this section I am going to demonstrate why “key” property is so important and how to use VDOM for efficient patching of existing DOM.
- Components: Final section will tell you about React Components and their creation, lifecycle and rendering procedure.
Each section will end with a live example, so you could instantly check all the progress we made so far. Lets get started.
Elements
Element is a lightweight object representation of an actual DOM. It holds all vital information — like node type, attributes and children list — so it can be easily rendered in the future. Tree-like composition of elements is called VDOM — example of one is shown below:
Instead of writing that monstrous object every time, most of React developers use JSX syntax, which looks like a neat mix of JavaScript code and HTML tags:
In order to get executed it needs to be transpiled into regular function calls — notice that pragma comment which defines what function should be used:
Finally, desired function is called during runtime — and it’s supposed to return a VDOM structure described above. Our implementation is going to be short — but despite of looking primitive, it serves its purpose perfectly:
First live playground is available here — it contains method described above with a couple of VDOM trees generated by it.
Rendering
Rendering is a process of turning VDOM into actual visible DOM. Generally, it is a pretty straightforward algorithm which traverses down the VDOM tree and creates respective DOM element for each node:
Code stated above may look scary, but we can make things less complicated by splitting it into smaller parts:
- Custom Attribute Setter: Properties passed to VDOM are not always valid in terms of DOM — things like event handlers, key identifiers and values must be treated individually.
- Primitive VDOM rendering: Primitives — like strings, numbers, booleans and nulls — are turned into plain text nodes.
- Complex VDOM rendering: Nodes with string tag are turned into DOM elements with recursively rendered children.
- Component VDOM rendering: Nodes with function tag are handled separately — don’t pay much attention to that part, we are going to implement it later.
Second live playground is available here — it demonstrates a small example of rendering process.
Patching
Patching is a process of reconciliation of existing DOM with a freshly built VDOM tree.
Imagine you have some deeply nested and frequently updated VDOM. When something changes, even the smallest part — that has to be displayed. Naive implementation will require full render every time:
- Remove existing DOM nodes.
- Re-render everything.
That sucks due to efficiency reasons — building DOM and painting it properly is a pretty expensive operation. But we can optimise this by writing patching algorithm that will require less DOM modifications in general:
- Build a fresh VDOM.
- Recursively compare it with existing DOM.
- Locate nodes that were added, removed or changed in any other way.
- Patch them.
But then another problem pops in — computation complexity. Comparison of two trees has O(n³) complexity — for example, if you are going to patch thousand elements — that will require one billion comparisons. Way too much. Instead, we are going to implement a heuristic O(n) algorithm that makes two major assumptions:
- Two elements of different types will produce different trees.
- The developer can hint at which child elements may be stable across different renders with a “key” prop.
In practice, these assumptions are valid for almost all practical use cases. Now we are ready for another portion of code:
Lets investigate all possible combinations:
- Primitive VDOM + Text DOM: Compare VDOM value with DOM text content and perform full render if they differ.
- Primitive VDOM + Element DOM : Full render.
- Complex VDOM + Text DOM : Full render.
- Complex VDOM + Element DOM of different type : Full render.
- Complex VDOM + Element DOM of same type : The most interesting combination, place where children reconciliation is performed, see details below.
- Component VDOM + any kind of DOM: Just like in the previous section, is handled separately and will be implemented later.
As you can see, text and complex nodes are generally incompatible and require full render — fortunately that’s a pretty rare mutation. But what about recursive children reconciliation — it performed as following:
- Current active element is memoized — reconciliation may break focus sometimes.
- DOM children are moved into temporary pool under their respective keys — prefixed index is used as a key by default.
- VDOM children are paired to the pool DOM nodes by key and recursively patched — or rendered from scratch if pair is not found.
- DOM nodes that left unpaired are removed from document.
- New attributes are applied to final parent DOM.
- Focus is returned back to previously active element.
Third live playground is available here — including small example of a list reconciliation.
Components
Component is conceptually similar to JavaScript function — it takes arbitrary input called “props” and returns set of elements describing what should appear on the screen. It can be defined as a stateless function or derived class with own internal state and set of methods and lifecycle hooks. I am going to be short in theory — let the code speak for itself:
Static methods are supposed to be called internally:
- Render: Performs initial rendering. Stateless components are called as a regular function — result is displayed immediately. Class components are instantiated and attached to the DOM — and only then are rendered.
- Patching: Performs further update. Sometimes DOM node already has a component instance attached to it — pass new properties to it and patch differences. Perform full render otherwise.
Instance methods are meant to be overridden or called within derived classes defined by user:
- Constructor: Handles properties and defines initial state, storing them within itself.
- State modifier: Handles new state, fires all required lifecycle hooks and initiates patch cycle.
- Lifecycle hooks: Set of methods that are fired throughout component life — on mount, during updates and just before it gets removed.
Notice that render method is absent — it’s meant to be defined in child classes. Final live playground is available here — with all code we made so far together with a simple to-do application example.
Conclusion
That’s all folks — we have a fully functional React clone now. I am going to call it Gooact — that would be a little tribute to my good friend. Lets take a closer look at the results:
- Gooact can build and efficiently patch complex DOM trees using VDOM as a reference.
- Gooact supports both functional and class components — together with proper internal state handling and complete lifecycle hooks set.
- Gooact consumes transpiled code produced by Babel.
- Gooact fits in 160 lines of uncompressed JavaScript code.
The main purpose of this article was to demonstrate core principles of React internal structure without deep diving into auxiliary APIs— that’s why some of them are missing in Gooact:
- Gooact doesn’t support things like fragments, portals, contexts, references and some other things that were introduced in the newer versions.
- Gooact doesn’t implement React Fiber due to its complexity — but I suppose I can write article about it in some point of future.
- Gooact doesn’t track duplicate keys and that may cause bugs sometimes.
- Gooact lacks support of additional callbacks for some methods.
As you can clearly see, that’s a great field for new features and improvements — repository is available here, so do not hesitate to fork and experiment. You can even install it using NPM!
I want to thank the whole React Team for making an amazing library, which made life of thousands developers way easier. Special credit goes to Preact original author Jason Miller — this article was heavily inspired by the minimalistic manner it is done.
Please share and recommend this tutorial if you enjoy it — that would help me writing more articles like this in the future. Thanks for reading!
Read my next article if you want to know more about blockchain internals: Goothereum: Cryptocurrency in 160 lines of JavaScript.