Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

This is a really thoroughly researched post and jlongster has my gratitude for writing it up.

I have two concerns with this approach. Take everything I say with a grain of salt as one of the authors of Ember.js.

First, as described here and as actually implemented by Om, this eliminates complexity by spamming the component with state change notifications via requestAnimationFrame (rAF). That may be a fair tradeoff in the end, but I would be nervous about building a large-scale app that relied on diffing performance for every data-bound element fitting in rAF's 16ms window.

(I'll also mention that this puts a pretty firm cap on how you can use data binding in your app, and it tends to mean that people just use binding from the JavaScript -> DOM layer. One of the nicest things about Ember, IMO, is that you can model your entire application, from the model layer all the way up to the templates, with an FRP-like data flow.)

My second concern is that components libraries really don't do anything to help you manage which components are on screen, and in a way that doesn't break the URL. So many JavaScript apps feel broken because you can't share them, you can't hit the back button, you can't hit refresh and not lose state, etc. People think MVC is an application architecture, but in fact MVC is a component architecture— your app is composed of many MVCs, all interacting with each other. Without an abstraction to help you manage that (whether it's something like Ember or something you've rolled yourself), it's easy for the complexity of managing which components are on screen and what models they're plugged into to spin quickly out of control. I have yet to see the source code for any app that scales this approach out beyond simple demos, which I hope changes because I would love to see how the rubber hits the pavement.

It's always interesting to see different approaches to this problem. I don't think it's as revolutionary as many people want to make it out to be, but I've never been opposed to borrowing good ideas liberally, either. Thanks again, James!



Thanks for the rational response, Tom. I hope this doesn't get buried (someone is going through and downvoting at least all of my comment to 0).

Blog posts are best when they are sensational, and I try not to overdue it. I think React has a lot of good ideas, but "revolutionary" is a strong word. I think "refreshing" is a better word. Regardless, I think both React and Ember are the best 2 solutions out there right now, with quite different philosophies, and I'm happy that users have a choice.

Using rAF in my post was pretty much a hack. I think it was fun to take that and run with it. When you use React though, you don't actually do that, you use its `setState` method, or you use something like Cortex. If you look at my cortex example, you do use setters and getters, which give you a way to notify data change. Why don't we just use models like Ember? Because React still doesn't care how we model our data -- even if we have to call `set()` to trigger a paint update, what we get is the choice to use something like persistent data structures for our models.

=== I was completely wrong about Om, it does not continuously trigger rerendering/diffing ever 16ms with rAF. It only uses rAF to batch rendering, so multiple repaints are throttled to a minimum of 16ms ===

The on-screen issue is interesting; I need to think about it more to see if we can actually leverage it in production apps. I think we can for large list views. You don't share scroll state, do you? I absolutely agree that too many JS apps are breaking the web, and I love that Ember has defaults to make that not happen. There is a grave danger in using React and not taking care to do things right.

I'm actually really, really happy about the idea of React and Ember being the 2 ways to choose to build webapps. I have the upmost respect and love for Ember, I think it does a lot of things right, and I wouldn't be surprised if things like routing wasn't copied for libraries to use for React. <3


I think this technique is really cool but I thought I should point out that although continuous rAF makes it easy (you can set state directly), on but FF and Chrome on my machine, even when your page appears to be completely static it's consuming 10-20% of CPU constantly.

That's got a couple of issues.

1. It eats my battery

2. It DoSes my machine (10-20% not available for other things)

So, please don't do that. Set a `dirty` flag, call a `setState` or something that requests just 1 rAF or queues a task to re-render or something and then go back to sleep.


It's really not meant to be something to use in production. It was fun to run with it, but you're absolutely right. In fact, if you look at my Cortex example I do just that. You will most likely want to use a data structure that knows when something's changed anyway.

But it was a neat way to go from something very simple to more complex.


it's consuming 10-20% of CPU constantly

It appears you're using a multicore machine with between 5-10 cores. A continuous loop is basically going to keep one core at 100%, which is only a bit of an annoyance with a multicore, but on a single or even dualcore machine (e.g. low-end mobiles), it will make everything else slow to a crawl.

It's interesting to note that, were this done several years ago or earlier, it would only make people complain about how your site froze their browser, but now we barely notice a single core running at full load unless we're observant...

Please use computing resources responsibly.


A continuous loop is basically going to keep one core at 100%, which is only a bit of an annoyance with a multicore, but on a single or even dualcore machine (e.g. low-end mobiles), it will make everything else slow to a crawl.

Not to mention that, even on a high-end laptop, that will drain the battery very quickly and burn your lap in the process.


It's not really using 100%. rAF means they do their work, then it goes to sleep until the next vblank. They then get their rAF event, check if they want to make any changes to the DOM, queue another rAF and then go back to sleep. So it really is only 10-20%. But it's still 10-20% :-(

I would be curious why it's at 10-20% though. If they don't touch the DOM and their checks are quick (just a pointer as the article says) then there's some crazy serious overhead in the browser or somewhere just to call into JS once every 16-17ms and have it go back to sleep.

It would be interesting to look into where 10-20% is going. It seems like in a perfect impl, a mostly no-op rAF should take 0.001% or something along those lines.


To clarify, Om does not "spam" state change notifications via requestAnimationFrame - all rAF calls are batched. For example if a state change occurs we schedule re-render via rAF. If another state change occurs we will not call rAF since one is already queued. In fact no state changes can queue another rAF until all changes in the current cycle have been completely flushed.


I think I said that it did in my post, and I was wrong. Will update


I guess this discussion hinges carefully on our relative definitions of "spam." ;) What I mean to say is that, changes or not, the browser will be invoking your diffing algorithm every 16ms.


It will not be invoked every 16ms, my comment above was an attempt to explain that.


Won't that only happen if a state change has been made? That is, the diffing algorithm will only get invoked once every 16ms if the state changes faster than once every 16ms.


Nope, it calls it regardless of state change—that's the point, you eliminate the challenge of determining when state has changed.

To quote TFA:

> A set method to change state could trigger a rerender (React has setState), but there's an even easier way to react to changes: continuously call renderComponent with requestAnimationFrame to repaint the UI, and only change the DOM when the component returns different content.

Edit:

I was mistaken about how Om works. I was under the impression that Om scheduled a rAF every 16ms to check for state change. In fact, it only schedules a rAF once the state changes.

However, this relies on being able to listen for state change from the underlying model data. I was under the impression that one of the benefits of Om/React was that you could use its data bindings with any JavaScript library, even if it hadn't been instrumented for property observation. Instead, it seems that with Om, you are limited to consuming data structures that implement its state change notification interface.


Your conclusions about how Om works are still inaccurate :)

There is no such limitation about what data structures you can use. There is no state change notification interface for data. Components can update their local state - no notification required here. Updates to global information (the application state) triggers a re-render from the root component of the application. Because of persistent data structures these updates can be done in logarithmic time as we never compute React UI subtrees that depend on unchanged data - it's just a reference equality check (JavaScript ===) in ClojureScript for any given piece of data to know whether it's changed or not.


Thanks for clearing up the confusion. Although, I think it's worth it for you to edit your initial reply since it's the most upvoted comment so far.

Also, your comment, to me, shows there is still some confusion regarding Om's internals:

> However, this relies on being able to listen for state change from the underlying model data. [...] Instead, it seems that with Om, you are limited to consuming data structures that implement its state change notification interface.

Actually, the interface used is ClojureScript's flavor of STM: you set your app state, generally a hash-map, in an atom. When you mutate the app state via `swap!` it is published to Om -> React -> render. This may seem like a pedantic distinction, however, the key point is that you are required to use an atom which controls app state mutation. You could also use strings and vectors as your app state if you so choose.


Interfacing with an external js library that publishes changes would be trivial though. Typically if you're building a ClojureScript app you're building a ClojureScript app... which means using atoms.


That's a description of the examples that use "Bloop," jlongster's toy library. Those examples do run a continuous requestAnimationFrame loop.

Om, however, does not call requestAnimationFrame in a continuous loop; it calls it only when the state changes.

(To be fair, jlongster's article originally said that Om also rendered continuously, but that has now been corrected.)


He's describing using rAF essentially as a form of throttling, to make sure you render at-most once per frame, but usually much less often.


I think a lot of people see frameworks like Ember and Angular and get scared by their vastness. You don't "get" them in one evening. I have spent many hours poring over documentation, articles and videos for both Angular and Ember and I still feel that there's more to learn. But things like routing, data persistence and controllers will be a part of your app even if you use React.

What I find good about Ember/Angular is that they are very particular about how your app should be constructed. When I started writing single page JS apps, I didn't know what the best practices were. If I had started with Ember, I would've learnt. If I had started with React, I would have been making it up as I went along. If you know what you're doing, maybe that's good. For me, I find that I really like having Ember tell me how to structure my app.


That's a very good point. I've currently settled on Backbone to give my stuff a bit more structure, but I suspect some of the complication of construction might actually disappear when using React. Not all of it, of course, but some of it.


You'll find a surprising amount of it will simply disappear. I'm at the point where I removed my models and collections directories entirely; since the only feature they provide me at this point are getting data from the server (which I can do with just jQuery, superagent, etc.)


And then you manage it in what way?


In a simple React app (ie most apps), you end up storing all your models (as Javascript Objects/Arrays) in one single scope (the "App/Main Component"). As a result you don't need to share things around / access them / listen to them from many different files. They no longer need "managing". I hope that helps.


I would be nervous about building a large-scale app

I have a Javascript game client that does rendering optimization by comparing a 79x25 grid of numeric values with JSON data it receives from a server. That is just short of 2000 items, and it is enough to stymie older versions of Firefox running on Core Duo machines. I also queue things, and in fact, the "diffing" and the rendering happen in two different rAF frames, and it's still a challenge for older versions of Firefox running on Core Duo machines.

I wonder if this sort of benchmarking been done on different machines and different versions of the various browsers?


In the case of Om, I believe the immutability of clojure helps out and makes the diffing of databound elements a simple (non-deep) compare.

You can end up making an app with quite a number of elements flying around.

A neat pixel editor example: http://jackschaedler.github.io/goya/




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: