From Backbone to React: Our Experience Scaling a Web Application
techsonian.net
techsonian.net
Say we have a try of data, Rows -> Row{name, id}
Say we have a component, <Row row={row} />
On the Row, component, we want to update our "own" name, so with mutable stuff, (ignoring state updates), we could set this.props.name = "potato".
Say the structure of rows was managed by a statemanager, then how do we get that to do it?
Does rows do something like this.props.updateRowName(this.props.row.id, newName), then the statemanager "queries" the rows and somehow replaces the entire dataset tree with a new one where that specific row's name has been changed?
Really fascinated to know, as I'd love to move to this model.
Om seems lovely, but I'm not comfortable enough with Clojure(script) to move to it, let alone force it upon others in a team.
Exactly. The state manager has "transactions" which are just functions passed down to components. These transactions are transformations on the entire application state, so to change the state you call them with data coming from user interactions and whatnot.
When you update a node, a whole new tree must be created with the new state.
It isn't as frightening as it sounds, the statemanager only needs to recreate from the root of the tree to the node that has changed. The rest of the structure can be shared between the new state and the old state as there are no changes.
It is a shallow but wide tree so we don't actually have to recreate that many pointers. In ClojureScript we have a maximum depth of 7.
The beauty of it all is because we have a new root every time one of the nodes is changed, if we need to check for a change we only have to compare a single node - the root. This makes for incredibly fast updates.
Hopefully (and presumably) this is true of any nodes nested in the tree as well, so that you can use the equivalent of the React PureRenderMixin to ensure that nested components only get re-rendered if one of their props has changed.
As for how you get the change request from your low-level component to that one place, there are a few options.
- Pass callbacks down to lower-level components in their props, as you show.
- Pass lower-level components a data structure that knows how to update itself within the larger data structure (a cursor, as used in Om)
- Allow components to issue change requests that get interpreted by the state manager. (See this article on how you can do typed change requests in Elm: https://gist.github.com/evancz/2b2ba366cae1887fe621)
Think of it as being similar to the classic one request at a time LAMP app. You query data from the database, then a template turns that into displayed content. When you want to change the data, you send an update transaction and then redo the query and the rendering to html.
It's just as simple here. The application state is like our database. We don't change it in part, down in some particular object, we write transaction functions that take the entire state to a new state. What's neat is our code is conceptually very simple: just functions that take take state as input and render a view. It might seem this would be absurdly slow, but the implementation doesn't have to be so naive. React does diffing under the hood to figure out the exact minimum changes needed to update the DOM after the state changes. Mori gives you similar optimizations when changing the state itself.
I need examples, basically!
Ideally I'd have an API to my datastore, which in turn would handle telling React what I've updated.
It feels like I'm just not "getting" something with Mori.
Say our state looks like this in a normal JS structure:
appState = {
widgets: [{name: 'blah', data: {'text': 'hello', articles: [{}]}}, ...}],
pageTitle: 'Hi!',
user: {name: 'Derp'},
notifications: 5
};
How would I even represent that with Mori?Say I got a push from the server that I had 6 notifications, how would I change the state to represent this?
Say I wanted the widget to do an API call to update it's articles, and add any new article data to that. How would I do this?
- you can clone-and-mutate (I started here, but it quickly became a bottleneck in my app, and jr developers forget a lot leading to hard to find bugs)
- you can use real persistent datastructures (Mori), but now you have added a cross cutting concern - which type of array is the one i'm working with right now?
- you can use React.addons.update (I use this successfully, but you need to get used to a weird syntax)
Om provides an abstraction over React state called Cursors which provides immutable updates to react state at any point in a state subtree. I ported this concept to javascript (implemented in terms of React.addons.update) and have used this successfully in two large scale enterprise applications for regulated industries and I know of one other company using this. This provides a very clean and elegant programming model for immutable updates to react state.
https://github.com/dustingetz/react-cursor/ https://github.com/dustingetz/react-cursor/blob/master/examp...
As a rule of thumb, anything that wants to reference and mutate "its own" state should go in its own component and that state should live in the state, not in the props (props want to be immutable).
If something doesn't need to keep a hierarchical chunk of state then it doesnt need to be split up into a component class. Perhaps a `function render_row()` that returns virtual dom nodes is enough.
In your case, it might be simpler to keep all your state in the parent Table instead of splitting it into Rows.
-----
> Does rows do something like this.props.updateRowName(this.props.row.id, newName), then the statemanager "queries" the rows and somehow replaces the entire dataset tree with a new one where that specific row's name has been changed?
At one point of the other, you are going to call setState() on some react component. In this example, this component could be the row itself the parent table or something higher up, depending on how you structured your app (there are pros and cons to each alternative here). Once you call setState() on that component, react will redraw the virtual DOM for that point down. Then, React detects where the virtual dom changed and emits real DOM mutations to update the screen.
In your question, if the parent table gets its state mutated, then it won't query the rows - it will just redraw all of them. This is not a big of a deal: redrawing virtual DOM is supposed to be cheap and you can use shouldComponentUpdate as a performance hint if you know that a certain subtree wont need to be updated.
-----
Finally, there are design patterns that deal with mutating immutable data structures (lenses, cursors, etc) but its a bit of an orthogonal issue. When you use this the idea is that you have a single mutable state thing at the root instead of a hierarchical set of state components (or at least thats how I understand it goes - I dont know Om in depth)
1: http://marionettejs.com/docs/marionette.application.html#the...
There is also a lot of other cool things coming in 3.0. We're very focused on the architectural pieces and theres some really great ideas there. See: https://github.com/marionettejs/backbone.marionette/issues/1...
I've had some other experience, I built a beta site for a startup using regular Backbone. I've also built a pretty complex site using Batman.js but it never got to see the light of day. Other than that, I've just toyed with Angular and Ember.
Side note: We're really rapidly re-writing our documentation right now (yay alliteration) so people can expect to see a lot of big improvements there.
You guys have one heavily-referenced example for your CompositeView. However, I believe some of the things in that tutorial are since deprecated (such as appendHtml):
http://lostechies.com/derickbailey/2012/04/05/composite-view...
The current documentation on composite view actually references that article.
Other than that, great work!
Recently, I switched a small project from Backbone/Marionette to React and I haven't looked back. The learning curve for React is tiny compared to Backbone/Marionette, and it is so much easier to 1. reason about and 2. recompose. Not to mention much better performance!
It's clearly still maturing, but to me React feels like a big leap ahead of Backbone/Marionette.
But an architecture that is newer and learned from other architectures is probably a good thing too.
And React / Backbone / or whatever will not be the last architecture.
ex. https://github.com/Khan/perseus
React's blog usually has some open source projects featured as well - facebook.github.io/react/blog
What is everyones experience with just using Backbone Models as your data source. We currently just pass the backbone models as props, but include a mixin that forces re-renders by listening to the model's change event.
I can see how at very large scales you'd want to move away from that, but for us it works great so far as we don't have to think that much about events, components are still just simply representations of the model in a functional way.
Does anyone know if its possible to seperate that in such a way that Angular or knockout works?
Or am I just not "enlightened" about the way it works? Which is also certainly possible.
I invite you to watch "Rethinking best practices", an introduction of what React is and why it was built so: https://facebook.github.io/react/docs/videos.html
You learn that separation of concerns is a good thing, but separating HTML and Javascript is not separation of concerns; it's just an arbitrary separation of technologies.
The mindset of React is that you build independent components, where each one contains everything to render information for the user. Whether you use 1, 2 or 3 technos (HTML+CSS+Javascript) for this component is not an issue and there's no reason to separate them.
for user in users
UserComponent(user)
is much better than some combination of `ng-repeat`, `ng-repeat-start`, `ng-repeat-end`Compared to other virtual-dom/js-as-a-templating-language tools, the most unique thing about react is the way they handle having a hierarchy of stateful components. Virtual dom is very simple if you only have state at the root level and rerender 100% of the page when something changes but it gets a trickyer if you want to distribute the state in a hierarchical manner.
I'd also much rather use a restricted templating engine like Handlebars so that my templates are forced to be "logic-less". I really don't think my template should have access to all of javascript.
But then, I like the full javascript approach from the mithril framework too and I haven't build something complex to date.
https://github.com/Raynos/mercury
it claims to be even more faster and maintainable than React.
I did run into some performance problems (rendering huge lists into complex tables), but I was able to iron them out for the most part. The only downside, which is definitely not unique to Vue, was debugging--a ton of functions get called in the Vue code when model data changes, so it can be tricky to find the right combination of breakpoints to set to track down why what you're expecting to happen isn't happening.
That seems like it's of low value as a benchmark.