React Virtual DOM is SLOW, try Million.js
Virtual DOM is an important feature in React that enables reactivity, but at the same time, it can be a performance bottleneck. The MillionJs library helps to mitigate this and can boost React performance by up to 70%.
One thing we must realize is that React is popular not because it’s fast. I discussed this in detail in my previous article, so make sure you check it out first. The number one factor that causes slow performance issues in React is the virtual DOM bottleneck. That’s why the community has started building a lot of React extension frameworks/libraries, such as Preact and SolidJs, that have different approaches to optimize rendering.
However, these frameworks require additional knowledge, code migration, and have some feature limitations. They also have smaller communities compared to React.
How Virtual DOM works?
React utilizes a Virtual DOM (Document Object Model) as an in-memory representation of the actual DOM. The Virtual DOM serves as a lightweight copy of the real DOM, consisting of a hierarchical tree structure of HTML elements, and is used for efficient updates of the actual DOM.
Whenever a component’s state or props change, React generates a new Virtual DOM representation of the component’s user interface (UI). This fresh Virtual DOM is then compared to the previous Virtual DOM, aiming to identify the minimum set of changes required to update the actual DOM. This process is known as reconciliation.
Reconciliation involves comparing the new Virtual DOM tree with the previous one and pinpointing the differences between them. React subsequently calculates the minimal set of changes necessary to update the actual DOM and applies these modifications accordingly.
To execute the reconciliation process, React employs a diffing algorithm that traverses both Virtual DOM trees and compares the nodes at each level. This algorithm discerns discrepancies between the two trees, such as nodes being added or removed, and subsequently updates the actual DOM accordingly.
React streamlines the reconciliation process by minimizing the number of updates required for the DOM. For instance, it may bundle multiple updates together or utilize a concept called “key” to identify which elements have changed and which remain unchanged. By reducing the number of updates made to the DOM, React ensures efficient UI updates without incurring performance issues or unnecessary renderings.
In simple words, React compares all the nodes one by one between the real DOM and the virtual DOM and chooses how to effectively render only the changes. Although React already has a heuristic approach to optimize the comparison algorithm from O(n³) to O(n), this comparison process still becomes an overhead in rendering.
Many libraries and frameworks try to improve this by implementing different approaches. One example is Svelte, where it has a build-time compiler to determine how state changes affect which nodes effectively. Another famous example is SolidJs, which implements a signal event emitter that emits changes to all affected nodes through subscriptions.
However, all of these approaches are separate from the original React application, where syntaxes are different, and major refactoring is needed for products to migrate from them. The MillionJs library helps improve React performance, and it’s really easy to integrate with existing React applications.
What is MillionJs?
MillionJs is an incredibly fast and lightweight Virtual DOM that can enhance React performance by up to 70%. Unlike React’s virtual DOM, MillionJs VirtualDOM creates a mapping between the props and the DOM. This allows MillionJs to bypass the step of comparing nodes one by one and directly update the DOM when props are updated.
By minimizing the update step with a compact block tree and direct mappings to the DOM, Million.js achieves impressive benchmarks in the JS Framework Benchmark.
What sets MillionJs apart is its seamless integration and minimal requirement for major changes. In the next section, we will discuss a Simple HOC component that enables MillionJs Virtual DOM within a component.
Get Started
To get started, what you need to do is install million
library on npm and enable the compiler.
Installation
npm install million
Enabling compiler
NextJs compiler
import million from 'million/compiler';
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
export default million.next(nextConfig);
Webpack compiler
import million from 'million/compiler';
export default {
plugins: [million.webpack()],
};
`Block` and `For` Component
MillionJs comprises two main components. The first one is block
, which is a Higher Order Component (HOC) that enables MillionJs VirtualDOM in the wrapped component.
const BlockRow = block(<Row>)
Additionally, to optimize the rendering of a list of components, MillionJs introduces a For
component that functions similarly to React’s map
<For each={array}>{(item, index) => <BlockRow item={item} />}</For>
Block Rules
While getting started with MillionJs is straightforward, there are a few rules to follow for the proper functioning of MillionJs VirtualDOM:
- Declare the block component as a variable
In order for the MillionJs compiler to recognize the block component, it needs to be returned as a variable and cannot be directly imported.
console.log(block(<div />)) // ❌ Wrong
export default block(<div />) // ❌ Wrong
const Block = block(<div />) // ✅ Correct
console.log(Block);
export default Block;
2. return static deterministic component
MillionJs only works with deterministic components, meaning they can only return a single DOM structure. Multiple return statements are not allowed. Be caution when using custom UI libraries within a block component, as they may introduce non-deterministic behavior.
function Component() {
const [count, setCount] = useState(initial.count);
if (count > 10) {
return <div>Too many clicks!</div>; // ❌ Wrong
}
// ❌ Wrong
return count > 5 ? (
'Count is greater than 5'
) : (
<div>Count is {count}.</div>
);
}
const ComponentBlock = block(Component);
3. Use the “for” component to render lists of components
To further enhance the performance of block components, always use the “for” component instead of “map” when rendering lists of components.
For more details, You can check the full documentation here
https://millionjs.org/docs/next
In conclusion, while I firmly believe that React is an excellent and mature library with a large community, concerns about React’s performance often arise as applications grow larger. Alternative frameworks like Svelte, SolidJs, and others are promising, but they require more time to mature and expand their communities.
Although MillionJs has some limitations, it can be seamlessly integrated into any existing React application and is definitely worth trying to improve React performance. If you know of any React performance-boosting libraries, feel free to comment below. Cheers! :)
Thank you for reading. I will post new articles biweekly (hopefully). They are mostly related to my software engineering experience and my thoughts about social economics. If you have any questions or suggestions about what I should write next, please don’t hesitate to put them in the comment section. Don’t forget to support me by following me and subscribing to Medium using my referral link: https://andreassujono.medium.com/membership or even buy me coffee in the tip box below. If you want to keep up to date with the current tech news, follow my Twitter at @andreassujono2. Cheers :)