Delayed Reaction [My experience converting a jQuery/Knockout.js application to use the React library]
It's important to stay up-to-date with technology trends and popular frameworks. That was part of the reason I wrote this blog using Node.js and it's part of the reason I recently converted a project to use the React library. That project was PassWeb, a simple, secure cloud-based password manager. I wrote PassWeb almost two years ago and use it nearly every day. If you're interested in how it works, please read the introductory blog post about PassWeb. For the purposes of this post, the thing to know is that PassWeb is built on the popular jQuery and Knockout.js frameworks.
To be clear, both frameworks are perfectly good - but switching was a great opportunity to learn about React. :)
- Stage one migrated the interface from HTML to a JSX file. This conversion took advantage of the existing observability (via Knockout) of variables to inform the need for UI updates. Observability for application logic seems like a good companion to React's basic design (and efforts like MobX formalize this approach).
- Once the Knockout logic had moved to JSX, stage two removed the Knockout dependency completely by implementing a simple, drop-in replacement for the
- Finally, stage three removed jQuery DOM manipulation and network calls via the
ajaxmethod. Again, I implemented a simple, drop-in replacement for
ajaxusing the XMLHttpRequest API directly. (Other options weren't as small or dependency-free as I wanted.)
Having performed the bulk of the migration, all that remained was to identify and fix the handful of bugs that got introduced along the way.
While JSX isn't required to use React, it's a natural fit and I chose JSX so I could get the full React experience.
Babel provides excellent support for this via the React preset and was easy to work with.
const, and lambda expressions.
I only scratched the surface of ES2015, but it was nice to be able to do so for "free".
One thing I noticed as I migrated more and more code was that much of what I was writing was boilerplate to deal with the propagation of state to and from (observable) properties. I captured this repetitive code in three helper methods and doing so significantly simplified components. (Projects like ReactLink formalize this pattern within the React ecosystem.)
Something I was curious about was how performance would differ after converting to React.
For the most part, things were fast enough before that there was no need to optimize.
Except for one scenario: filtering the list of items interactively.
So I'd already tuned the Knockout implementation for better performance by toggling the visibility (CSS
display:none) of unfiltered items instead of removing and re-adding them to/from the DOM.
When I converted to React, I used the simplest implementation and - unsurprisingly - this scenario performed worse.
The first thing I did was implement the
shouldComponentUpdate function on the component corresponding to each list item (as recommended by the Advanced Performance section of the docs).
React's built-in performance tools are very useful and quickly showed the need for this optimization (as well as confirming the benefits).
Two helpful posts that discuss the topic further are Optimizing React Performance using keys, component life cycle, and performance tools and Performance Engineering with React.
shouldComponentUpdate was a good start, but I had the same basic problem that adding and removing hundreds of elements just wasn't snappy.
So I made the same visibility optimization, introducing another component to act as a thin wrapper around the existing one and deal exclusively with visibility.
After that, the overall performance of the filter scenario was improved to approximate parity.
(Actually, React was still a little slower for the 10,000 item case, but fared better in other areas, and I'm comfortable declaring performance roughly equivalent between the two implementations.)
Other considerations are complexity and size. Two frameworks have been replaced by one, so that's a pretty clear win on the complexity side. Size is a little murky, though. The minified size of the React framework is a little smaller then the combined sizes of jQuery and Knockout. However, the size of the new JSX file is notably larger than the templated HTML it replaces (recall that the code for logic stayed basically the same). And compiling JSX tends to expand the size of the code. But fortunately, Babel lets you minify scripts and that's enough to offset most of the growth. In the end, the React version of PassWeb is slightly smaller than the jQuery/Knockout version - but not enough to be the sole reason to convert.
Now that the dust has settled, would I do it all over again? Definitely! :)
Although there weren't dramatic victories in performance or size, I like the modular approach React encourages and feel it may lead to simpler code.
I also like that React combines UI logic and presentation better and allowed me to completely gut the HTML file (which now contains only
I also see value in unifying an application's state into one place (formalized by libraries like Redux), though I deliberately didn't explore that here.
Most importantly, this was a good learning experience and I really enjoyed getting to know React.
I'll definitely consider React for my next project - maybe even finding an excuse to explore React Native...