Ember.setState(this.get(React))

Alex Matchneer - 8/28 - Ember NYC

Who’s this guy

Alex Matchneer

@machty

Ember.js Core Team

ExpressCheckoutApp.com

You may remember me from ng-embereño

“This is the only intelligent comparison I have seen of Ember & Angular”

Troy McClure
Hacker News User

React.js

The V in MVC… kinda

About React

  • Open-sourced by Facebook mid-2013
  • Currently at version 0.11.1
  • Drives many widgets in Facebook and much (all?) of the Instagram web experience
  • GitHub’s Atom text editor uses it
  • Definitely some outspoken Ember->React converts

So… what is React?

  • Component-centric view layer
  • ...that is highly performant
  • …that encourages uni-directional data flow and facilitates many lovely functional patterns

So what?

Aren’t there like a million JavaScript view layers out there?

Yes.

But it’s important to understand the philosophy behind many of React’s design decisions.

React Postulate 1

Managing state is treacherous

State management is difficult

  • e.g. which radio button is selected, which user is logged in, which article are we looking at, whether a widget is currently being dragged

React Postulate 2

Two-way bindings are evil

Two Way Bindings are Evil

  • Transparency: effects of a code change are limited/local, or at least easy to reason about
  • Also a useful concept regarding state change
  • Two-way bindings open Pandora’s box:
  • No easy way of knowing how far-reaching a change in the reverse direction is going to be

Cue poor man’s XKCD

In other words...

Two-way bindings are like Ice Nine.

Read some Vonnegut if this makes no sense.

React Postulate 3

Data changing over time is the ultimate root of all evil

TL;DR: Everything is Difficult

So how is React designed to deal with all of this awfulness?

Uni-directional Data Flow: State vs Props

  • Components can either be passed data (props), or materialize their own state and manage it over time (state)
  • Passed-in props are immutable (no two-way binding, remember?)
  • Components explicitly modify their state via this.setState

But wait...

...data needs to come back upward somehow, right?

Like,

what if a parent renders a child form? How does it know when that form was submitted?

Callbacks

  • In order for a child to notify its parent of a state change, a parent must pass the child a callback as one of its properties
  • (I’ll talk about alternatives to this later)

Kinda like actions in Ember, right?

Yeah, naw, they’re just callbacks. They’re not events that keep bubbling or anything like that.

Cool

So what about state changes over time being the root of all evil?

In React, when you call setState...

...literally everything re-renders from that point downward

We’re talking giant arrays of stuff

OMG that sounds insane

Doesn’t that just kill performance?

No.

Doesn’t mess up scrolling?

No.

Doesn’t that mean needlessly tearing down and rebuilding DOM?

No.

WTF?

VIRTUAL

DOM

Virtual DOM

  • JavaScript representation of DOM nodes, totally separate from the browser’s slow JavaScript/C++ DOM API
  • Makes server-side rendering possible (because Node doesn’t need to have a real DOM API)
  • And most importantly...

VIRTUAL

DOM DIFFING

Every time you call setState...

  • A new virtual DOM tree is generated
  • New tree is diffed against old…
  • ...producing a minimum set of changes to be performed on real DOM to bring it up to date

Hence

No unnecessary/expensive teardown of DOM

Hence

Scrolling doesn’t break (any more than it might with fine-tuned, granular DOM manipulation)

But you still rebuild / recalculate large objects / arrays?

Turns out JavaScript is fast, and that that part is rarely the bottleneck

What about diffing trees?

Isn’t that O(n^3)?

No, due to heuristics based on how you’ve built up your tree of components.

But first...

<Wat></Wat>

We have to talk about JSX

JSX is

  • A preprocessor: XMLish syntax -> JavaScript
  • Completely optional
  • Intended to make more sense to designers (and anyone more comfortable working with HTML, though it is not exactly HTML)

JSX Examples

JSX

JavaScript

It’s all JavaScript

We don’t need no templates

Templates

  • Templates often live in a separate file (e.g. .hbs)
  • Contain static and dynamic portions, e.g. “some text” vs “some {{value}}”
  • Dynamic portions “filled in” by whatever template context you pass in
  • It’s up to JavaScript portion of app to build up that context and expose it to the template

Templates/rendering in Ember

  • Handlebars is our templating engine
  • Controllers or Components are template contexts that fill in the dynamic bits
  • Helpers aside, it’s pretty difficult / often private API to do lots of DOM rendering in JavaScript

Rendering in React

  • There’s no “template context”
  • The “context” (the data you have access to while rendering DOM) is literally everything available in the render function, and whatever it closes over
  • ...it’s just JavaScript after all

React Example

React Example

React Example

React / JSX niceties

  • Minimal syntactic sugar over JavaScript, as opposed to new syntax/language
  • No need to re-implement features that already exist in JavaScript in your templating language of choice
  • e.g. ES6 / require imports
  • No template context abstraction

React / JSX drawbacks

  • Flow control (if blocks, loops), or anything involving lambdas, are awkward
  • Often they need to be kept visually separate from the rest of your render code

React / JSX drawbacks: example

React / JSX drawbacks: example

React / JSX drawbacks

Ember + Handlebars:

Because Handlebars isn’t “just JavaScript”, its syntax can stay presentation-focused; e.g. you don’t have to distinguish between lambdas and values

lambda

So, DOM in JavaScript…?

Seems like poor separation of concerns?

React authors argue the contrary:

Given the extremely tight coupling between the template and it’s context (a controller/component), the concerns are the same, and splitting the DOM into a template is an arbitrary separation of technologies rather than a legit separation of concerns.

Hence

In React, everything is a component.

OK, we get it.

Time to pop() the stack, but I feel it’s worth mentioning:

If you’re going to hate on React for some reason, make it something other than JSX

stack.pop()

[react, diffing heuristics, JSX]

stack.pop()

[react, diffing heuristics]

We were talking about virtual DOM diffing constraints and heuristics

There are some rules to abide by, now that we understand JSX.

Constraint 1: single element

  • Component.render must return a single element (with any number of children)
  • Tempting to want to return something like <div></div><div></div>, but that’s like returning div(),div() (and React doesn’t support returning arrays)

“Constraint” 2: dynamic portions will be auto-wrapped

  • “Hello, {name}!”
  • <span>Hello, </span><span>Alex</span><span>!</span>
  • (fyi: ember script tags are gone in 1.8 beta!)

Constraint 3: provide keys to large lists

Side note: this error rules

Constraint 3: provide keys to large lists: why?

You have a list of players, sorted by name and you insert a player into that list. Commence DOM diff

Constraint 3: provide keys to large lists: why?

You have a list of players, sorted by name and you insert a player into that list. Commence DOM diff

Constraint 3: provide keys to large lists: why?

Lesson: you need to provide React with key=”” information when rendering lists, or else it won’t have enough information to efficiently update when you swap out arrays

Constraint 4: keys are required in other use cases

  • Even when lists aren’t involved, you need to provide keys when swapping out similar components whose lifecycle hooks/setup you depend on
  • e.g. swapping out <input> fields; if you’re not careful React might not populate a swapped input field with a default value

That’s about it for specifics

At this point you should hopefully decently grok the basics of React architecture

So what do I think?

I thought you’d never ask

machty’s thoughtz

  • React is pretty damn cool
  • The “Re-render everything” programming model is a major achievement (thanks for proving it was possible, Facebook R&D)
  • Quite powerful relative to its simplicity

machty’s thoughtz

  • React Components are incredibly flexible; because they take you so much closer to the DOM, there are many widgets that are just straight up easier to write in React than Ember

If you love it so much why don’t you

steal all of its good ideas?

We’ll probably steal some ideas

but some of these good React ideas have some major caveats. Let us explore.

So, why does Ember

provide 2-way bindings if they’re so dangerous?

How evil are 2-way bindings, really?

and how crucial are they to the Ember programming model?

Two-way bindings can be evil, but...

  • They have their use cases
  • What is a one-way binding other than a two-way binding that you only use in one direction?
  • Ember supplies them, but the prevailing (route-driven) patterns encourage / facilitate uni-directional data flow

What are these so called use cases?

Input fields

  • It’s kinda really nice to just say
    {{input value=foo}} and not have to deal with change events, manually keeping things in sync
  • React recognizes this, provides helpers that feel like two-way bindings
  • React example

What about state change over time?

What does Ember do to help tame that beast?

First off, let’s just acknowledge that

we have clearly tamed that beast

Ember dominates the ecosystem of large apps

Whatever we’re doing, it seems to be working.

But specifically: Ember already has uni-directional data flow

and our bindings are live, so when upstream data changes, downstream changes too

But Ember has no unified setState

  • Key-value observation + set()ters fill the role of setState
  • setState is nice because it’s easily grep-able; you can quickly find all the points in your app where data changes
  • But for Ember->React converts, I don’t think this is actually a major sticking point

Unifying theme: there’s less typing in Ember

But many folk really like the explicitness of React.

“Is React scalable?”

“Yes, you dummy, obviously it’s scalable if Facebook uses it.”

but actually, it doesn’t take very long before you need to expand beyond the patterns built into React

Implications of Uni-directional flow

renderComponent(<App/>, rootElement)

(... miscellaneous top-level state …)

<CartDetailsPage />

<NavBar />

<NavCartOverview />

“5 items in cart

item 1

item 2…”

“5 items in cart”

Implications of Uni-directional flow

renderComponent(<App/>, rootElement)

(... miscellaneous top-level state …)

<CartDetailsPage />

<NavBar />

<NavCartOverview />

“5 items in cart

item 1

item 2…”

“5 items in cart”

Shared Cart Model

Implications of Uni-directional flow

  • Need a shared cart model for separate CartDetailsPage and NavCartOverview
  • Both components need to update when updates are made to cart

Implications of Uni-directional flow

  • This is a common problem in React
  • Specifically, if you want to share state between separate components, you have to 1) find the nearest common ancestor component 2) store the shared state on it, and 3) feed callbacks through all intermediate components to the two components (e.g cart item removal)

Implications of Uni-directional flow

  • This isn’t very scalable:
  • Your parent components will continue to bloat (fat interface problem)
  • Intermediate components now have added responsibility of forwarding through callbacks they don’t really care about

Don’t get me wrong: this pops up in Ember sometimes too

But in a majority of cases, this is solved by KVO and model objects that live on a global store

“Global store? Seems pretty easy to throw one of those into React”

Yes, but it means leaving the happy React land of uni-directional data flow

Also

it isn’t enough to have access to a shared model objects; you’ll also need to manually subscribe to change events for that model, etc.

Enter Flux

React’s escape hatch

Facebook Flux

  • “More of a pattern than a framework”
  • Many variants in the community (reflux, flocks, etc), but all involve some variation of a pub-sub model of initiating / listening for data changes

Facebook Flux

  • Not super important to understand a lot of the details (they also vary)
  • The key point is that you very quickly get to a point that you ditch the unidirectional data flow at the React component level and start stashing state in a sideways, global manner

Implications of Uni-directional flow

renderComponent(<App/>, rootElement)

(... miscellaneous top-level state …)

<CartDetailsPage />

<NavBar />

<NavCartOverview />

“5 items in cart

item 1

item 2…”

“5 items in cart”

Some kind of global store; sideways flow rather than flowing down from top component

Alright Fancy Pants, but what about Ember?

  • We know that strict uni-directionality is impossible for scalable apps
  • Hence, we put a lot of love into our APIs for making this manageable (Ember Data, the container API, dependency injection)

Alright, wrap it up

I have to mention some cool projects

React Router (formally react-nested-router)

  • github.com/rackt/react-router
  • Ryan Florence’s and Michael Jackson’s brainchild
  • Heavily inspired by Ember router
  • Still some churn, corner cases, gotchas, but extremely well done, and feels very at home in React yet familiar to Ember devs

Om

  • github.com/swannodette/om
  • Write apps in ClojureScript, render with react
  • React’s efficient re-render everything model is very friendly to immutability programming patterns

Facebook Immutable Library

  • github.com/facebook/immutable-js
  • Immutability patterns, but in JavaScript rather than ClojureScript (Om)

Final Thoughts

  • React is awesome
  • Try it out
  • Don’t be surprised if Ember adopts DOM-diffing

Thank you!

@machty

React vs. Ember - Alex Matchneer - EmberNYC meetup - Google Slides