1 of 115

Ember.setState(this.get(React))

Alex Matchneer - 8/28 - Ember NYC

2 of 115

Who’s this guy

Alex Matchneer

@machty

Ember.js Core Team

ExpressCheckoutApp.com

3 of 115

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

4 of 115

5 of 115

React.js

The V in MVC… kinda

6 of 115

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

7 of 115

So… what is React?

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

8 of 115

So what?

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

9 of 115

Yes.

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

10 of 115

React Postulate 1

Managing state is treacherous

11 of 115

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

12 of 115

React Postulate 2

Two-way bindings are evil

13 of 115

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

14 of 115

Cue poor man’s XKCD

15 of 115

16 of 115

17 of 115

18 of 115

19 of 115

20 of 115

In other words...

Two-way bindings are like Ice Nine.

Read some Vonnegut if this makes no sense.

21 of 115

React Postulate 3

Data changing over time is the ultimate root of all evil

22 of 115

TL;DR: Everything is Difficult

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

23 of 115

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

24 of 115

But wait...

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

25 of 115

Like,

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

26 of 115

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)

27 of 115

Kinda like actions in Ember, right?

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

28 of 115

Cool

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

29 of 115

30 of 115

31 of 115

In React, when you call setState...

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

32 of 115

33 of 115

34 of 115

We’re talking giant arrays of stuff

OMG that sounds insane

35 of 115

Doesn’t that just kill performance?

No.

36 of 115

Doesn’t mess up scrolling?

No.

37 of 115

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

No.

38 of 115

WTF?

39 of 115

VIRTUAL

DOM

40 of 115

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...

41 of 115

VIRTUAL

DOM DIFFING

42 of 115

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

43 of 115

Hence

No unnecessary/expensive teardown of DOM

44 of 115

Hence

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

45 of 115

But you still rebuild / recalculate large objects / arrays?

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

46 of 115

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.

47 of 115

But first...

48 of 115

<Wat></Wat>

We have to talk about JSX

49 of 115

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)

50 of 115

JSX Examples

JSX

JavaScript

51 of 115

It’s all JavaScript

We don’t need no templates

52 of 115

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

53 of 115

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

54 of 115

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

55 of 115

React Example

56 of 115

React Example

57 of 115

React Example

58 of 115

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

59 of 115

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

60 of 115

React / JSX drawbacks: example

61 of 115

React / JSX drawbacks: example

62 of 115

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

63 of 115

So, DOM in JavaScript…?

Seems like poor separation of concerns?

64 of 115

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.

65 of 115

Hence

In React, everything is a component.

66 of 115

OK, we get it.

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

67 of 115

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

68 of 115

stack.pop()

[react, diffing heuristics, JSX]

69 of 115

stack.pop()

[react, diffing heuristics]

70 of 115

We were talking about virtual DOM diffing constraints and heuristics

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

71 of 115

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)

72 of 115

“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!)

73 of 115

Constraint 3: provide keys to large lists

Side note: this error rules

74 of 115

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

75 of 115

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

76 of 115

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

77 of 115

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

78 of 115

That’s about it for specifics

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

79 of 115

So what do I think?

I thought you’d never ask

80 of 115

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

81 of 115

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

82 of 115

If you love it so much why don’t you

steal all of its good ideas?

83 of 115

We’ll probably steal some ideas

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

84 of 115

So, why does Ember

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

85 of 115

How evil are 2-way bindings, really?

and how crucial are they to the Ember programming model?

86 of 115

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

87 of 115

What are these so called use cases?

88 of 115

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

89 of 115

What about state change over time?

What does Ember do to help tame that beast?

90 of 115

First off, let’s just acknowledge that

we have clearly tamed that beast

91 of 115

Ember dominates the ecosystem of large apps

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

92 of 115

But specifically: Ember already has uni-directional data flow

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

93 of 115

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

94 of 115

Unifying theme: there’s less typing in Ember

But many folk really like the explicitness of React.

95 of 115

“Is React scalable?”

96 of 115

“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

97 of 115

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”

98 of 115

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

99 of 115

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

100 of 115

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)

101 of 115

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

102 of 115

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

103 of 115

“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

104 of 115

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.

105 of 115

Enter Flux

React’s escape hatch

106 of 115

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

107 of 115

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

108 of 115

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

109 of 115

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)

110 of 115

Alright, wrap it up

I have to mention some cool projects

111 of 115

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

112 of 115

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

113 of 115

Facebook Immutable Library

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

114 of 115

Final Thoughts

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

115 of 115

Thank you!

@machty