Hacker News new | ask | show | jobs
by jlongster 4353 days ago
This technique may be the most revolutionary thing in web development in the last several years, IMHO. I've been using React for a while, and starting to integrate Mori for persistent data structures, and the things I can do with it is insane. The fact that it's not only far better performant, but a way better abstraction for dealing with UIs, is crazy.
3 comments

If this technique is much more performant (batching diffs of the DOM), why don't browsers perform this "back-buffering" natively?
They do perform this "back-buffering" natively. When you manipulate the DOM in a modern (post-2009 or so) browser, it's just changing a pointer and flipping a dirty bit.

The problem is that it's very easy to force a full recalculate of the whole page layout. Whenever you call .offsetHeight or .offsetWidth or .getComputedStyle, you're doing it. The full list of properties is about 2 dozen strong:

http://gent.ilcore.com/2011/03/how-not-to-trigger-layout-in-...

Most web developers don't know this, and so they're actively making their pages slow. Worse, many popular frameworks build this into the library, and so if you use them, there is no way to keep your pages responsive. JQuery, for example, can easily cause 4-5 layouts with a single call to .css; on a mobile phone and a moderately complex page, that's about a second of CPU time.

This got me wondering how React handles this requirement. Can you use React if you need to know offsetWidth/Height to do complex layout?
Once the component is mounted (there's a componentDidMount callback) you have access to the real DOM node and can access them (or perhaps store them as a property if necessary). The DOM node is also available from event callbacks and such.
Not everything needs to be built-in. There are significant trade-offs for baking things into the browser, and it's actually far better to keep things as libraries. You don't have to worry as much about backwards compatibility, it's far easier to roll out updates, etc.

Also, the fact is that the web is stuck in a Web Components-driven approach to building apps which is pretty orthogonal to how this works.

They try their best, but often naive code will make changes and then read values in an order that requires recalculation to produce the right value.
What are you using Mori for that react.addons.update doesn't do?
react.addons.update is a poor man's implementation of something like mori. I'm not going to go into details right now (also still early in research), but mori is way more efficient and provides a better API for working with this kind of stuff (`sort` return a new obj, etc).
Can we improve react.addons.update then? Mori is not syntax compatible with javascript datastructures, and needs to be marshalled to interop with javascript components. If you include marshalling overhead, mori is in the same ballpark as react.addons.update.

http://jsperf.com/sprout-vs-mori/3

That's the thing I love about React though; you don't have to marshal if you embrace mori wholesale. I can render components based on these data structures and never have to "reify" them into real JS data structures.

That said, I'm probably being overly optimistic and I'm just starting to research it. I don't quite like how addons.update feels like a bandaid, but maybe it is good enough. Haven't done enough research yet. I definitely don't like writing updates the way addons.update forces you to, but sweet.js macros could solve that (and I was going to write macros for mori anyway).

I've done some research too - check out this implementation of cursors, it enables drop-in O(1) shouldComponentUpdate (like Om) and is compatible with rAF batching (like Om). We explored Mori for a bit too and ended up deciding Mori wasn't worth it. (Our app was big enough that we experienced pretty brutal performance without shouldComponentUpdate. Now our bottleneck is methods like Array.prototype.map over large lists in render, which Mori can't help with - we have to restructure our state away from lists and into trees to better take advantage of shouldComponentUpdate)

https://github.com/dustingetz/react-cursor/blob/master/examp... https://github.com/dustingetz/react-cursor/blob/master/js/Cu...

(It is backed by react.addons.update, it provides a mechanism like mori.assoc_in for immutable subtree updates, and it preserves reference equality for equivalent cursors (value/onChange tuples))

(Cursors are also not vulnerable to issue#122 https://github.com/facebook/react/issues/122)

Very cool! I had researched this technique before, when I found cortex: https://github.com/mquan/cortex. Cortex does something very similar, except it doesn't actually do persistent data structures. Building cursors off of the addons.update stuff is a neat idea. (why `onChange` instead of `set` and `pendingValue` instead of just `value`?)

I agree that you can take this really far. At this point I need to sit back and thing about it. :) I think mori could provide better performance for certain types of apps, but it does come at a cost if interop. Time to hit the hammock.

This is really great work.

Why do all of this instead of just using Om? I'm currently waffling between Om and straight react.js for a project of mine.

The reason why Mori scales better is because it implements arrays via a tree of 32-slot arrays. So unfortunately, you cannot use normal JavaScript data structures and have to re-implement all the array methods that know how to deal with this data structure.

React.addons.update uses normal JavaScript arrays. So it won't scale as well, but at least you get immutability.

If your react state contains large lists, I think React's render() is going to be the bottleneck, not javascript arrays. That's why react state needs to be structured in a tree, so you can implement shouldComponentUpdate and get O(log n) renders. I wrote more in reply to jlongster.
Do you have any code samples online of this combination? I'd be interested to see how these are used together, because they seem like a great fit.
Not yet, I've been dying to dig into this for months but now just getting some time to do so. I'm rewriting my blog with lots of cool technologies like this and going to open-source it and write up about it. http://jlongster.com/
Just wanted to add my support with this - I am using React+Fluxxor+ampersand-collections, and I find myself simply rebuilding the collections on every change (and disabling their add/remove/set/reset functions) to keep them immutable and speed `shouldComponentUpdate`, but I would rather be using mori.

When you use something like mori, but you need to define data transforms, such as float precision, derived attributes, and so on, how do you make that work well in your apps without too much complexity in your components? One thing I really enjoy about ampersand-state and ampersand-collection (forks of Backbone) is that I can define very simple, standard functions per model so that my views don't have to have any idea what was passed to them. How would that be possible in Mori? Do you use something like a `__type` attribute so an external utility can suss out what the object is and transform it correctly?

I'm not familiar with ampersand-collection, so I don't exactly understand your questions, but I think you're also farther along than I am researching this. I can't give a good answer yet as I'm just starting to play around with integrating mori.

I don't know what you mean by "derived attributes", or why it would be difficult to do data transformations on the models that deal with mori data structures. It'd be great to discuss this somewhere, maybe on #reactjs IRC? I'm jlongster there.

Cool, I'll keep an eye out. Any reason to use React+Mori over Om other than more familiarity with JavaScript??
There is a huge amount of JavaScript developers that can't jump to ClojureScript. I love CLJS, but I have a heart for the amount of JS projects out there that can't take advantage of these techniques. It's really important that we borrow from good research and make it available to JS so that existing projects can begin integrating them, because most of them will not jump to a completely different language.
elm-lang.org has some really great demos. http://elm-lang.org/Examples.elm

I haven't dug deep enough to form an educated opinion on the language, but so far its refreshing to see such a drastically different process than popular languages today.