React, life without MVC
Priyatam Mudivarti
Principal, Facjure
ForwardJs 3
JULY 29TH, SAN FRANCISCO
Trygve Reenskaug
introduced MVC into Smalltalk-76 while visiting Xerox Parc in 1979.
Our industry has chosen code written in terms of classes. A class tells us everything about the properties of the individual objects that are its instances. It does not tell us anything about how these instances work together to achieve the system behavior.
30+ years of MVC
MVC was the first to describe and implement software constructs in terms of their responsibilities.
MVC used a set of protocols to define components instead of using concrete implementations.
However, many web developers find it hard to create a model up front. The system structure evolves as it is being shaped directly in code.
What's wrong with MVC?
Nothing!
but it can be limiting
MVC WAS ARCHITECTED FOR
ANOTHER AGE
realtime feeds, static lists, timed—batch sync, search ...
REACt
in a few lines of jQuery!
source: http://hackflow.com/blog/2015/03/08/boiling-react-down-to-few-lines-in-jquery/
Demo (simple)
two inputs, one output
<span id="colored-counter">0</span>
<input id="color"></input>
<button id="inc"></button>
$('#color').on('keyup', function () {
$('#colored-counter').css('color', this.value);
})
$('#inc').on('click', function () {
var oldValue = $('#colored-counter').html();
var newValue = 1 + Number(oldValue);
$('#colored-counter').html(newValue);
})
Demo
multiple inputs + outputs, undo
can't scale
M*N (events, elements)
Refactor #1
introducE state
/*
<span id="colored-counter">0</span>
<input id="color"></input>
<button id="inc"></button>
*/
var state = {color: '', value: 0};
function updateUI() {
$('#colored-counter').css('color', state.color);
$('#colored-counter').html(state.value);
}
$('#color').on('keyup', function () {
state.color = this.value;
updateUI();
})
$('#inc').on('click', function () {
state.value++;
updateUI();
})
Refactor #2
serialize state
function updateUI() {
// Save latest state to local storage
LocalStorage.set('state', JSON.stringify(state));
// ...
}
// Load saved state from local storage on page load
$(function () {
state = JSON.parse(LocalStorage.get('state'));
updateUI();
});
Refactor #3
state = data structures
Instead of copying and mutating state, build new state
based on previous state
+---+ +---+ +---+ +---+ | 4 +--->+ 3 +--->+ 2 +--->+ 1 | +---+ +---+ +---+ +---+ | | newList original
source: http://www.jayway.com/2013/03/03/git-is-a-purely-functional-data-structure/
+---+ +---+ +---+ +---+ new list 1 -> | 4 +---+->+ 3 +--->+ 2 +--->+ 1 | +---+ / +---+ +---+ +---+ / | +---+/ original new list 2 -> | 9 + +---+
source: http://www.jayway.com/2013/03/03/git-is-a-purely-functional-data-structure/
+---+ +---+
updated list -> | 4 +--->+ 5 +----+
+---+ +---+
+---+ +---+ +-+-+ +---+
new list 1 -> | 4 +--->+ 3 +--->+ 2 +--->+ 1 |
+---+ / +---+ +---+ +---+
/ |
+---+/ original
new list 2 -> | 9 +
+---+
source: http://www.jayway.com/2013/03/03/git-is-a-purely-functional-data-structure/
preserve state on page reloads
serialize history to backend
reproduce errors users face
go back in time
/*
<span id="time-pos"></span>
<button id="back">Back</button>
<button id="next">Next</button>
*/
var time = {history: [], pos: 0};
function updateTimeUI() {
$('#time-pos').html('Position ' + time.pos + ' of ' + time.history.length);
}
function saveState() {
time.history.push(deepcopy(state));
time.pos++;
updateTimeUI();
}
$('#back').on('click', function () {
time.pos--; // Move history pointer
updateTimeUI();
state = deepcopy(time.history[time.pos]); // Load historic state
updateUI();
})
$('#next').on('click', function () {
time.pos++;
// ...
})
function updateUI() {
saveState();
// render compontents
}
Refactor #4
Optimize rendering
render(appstate) => HTML
function render(state) {
var span = '<span id="count">' + state.items.length + '</span>';
var lis = state.items.map(function (item) {
return '<li>' + item + '</li>';
});
return span + '<ul>' + lis.join('') + '</ul>'
}
function updateUI() {
$('#ui').html(render(state));
}
Refactor #5
save memory:
persistent data-structures
instead of N, create logN new objects and reuse: faster diffs.
source: http://jr0cket.co.uk/slides/functional-advantage-with-clojure.html
var object = {
a: {x: 1, y: 2},
b: {text: 'hi'}
}
// Now instead of object.a.y = 3 we do
var object2 = {
a: {x: object.a.x, y: 3},
b: object.b
}
// Suppose you have an object with many keys and a value of one of them changes
var prev = ['a', 'b', 'c', ..., 'z'];
var next = prev.slice(0, n-1).concat([newValue]).concat(prev.slice(n));
// slow!
var prev = {
'0-3': {
'0-1': {0: 'a', 1: 'b'},
'2-3': {...},
},
'4-7': {...}
}
var next = {
'0-3': {
'0-1': prev['0-3']['0-1'],
'2-3': {
2: 'hey',
3: prev['0-3']['2-3'][3]
}
},
'4-7': prev['4-7']
}
// Turns out many immutable/persistent ds implementations exist
// Facebook also has one: ImmutableJs
var object = Immutable.fromJS({
a: {x: 1, y: 2},
b: {text: 'hi'}
})
var object2 = object.setIn(['a', 'y'], 3); // object remains intact
Refactor #5
minimize dom mutations
abstract/virtual dom
var root = document.getElementById('ui');
var prevState = state, prevTree = [];
function render(state) {
// Virtual DOM is really just a tree of JavaScript objects or arrays
return [
['span', {id: 'count'}, state.items.length],
['ul', {}, state.items.map(function (item) {
return ['li', {}, item]
})]
]
}
function updateUI() {
var vTree = render(state);
var diff = vDiff(prevTree, vTree); // Just a diff on data structures :)
vApply(root, diff) // Apply series of patches to real DOM
prevState = deepcopy(state);
prevTree = vTree;
}
who has time to optimize this
while (true) { processInput(); update(); render(); }
var ToggleText = React.createClass({
getInitialState: function () {
return {
showDefault: true
}
},
toggle: function (e) {
// Prevent following the link.
e.preventDefault();
// Invert the chosen default.
// This will trigger an intelligent re-render of the component.
this.setState({ showDefault: !this.state.showDefault })
},
render: function () {
// Default to the default message.
var message = this.props.default;
// If toggled, show the alternate message.
if (!this.state.showDefault) {
message = this.props.alt;
}
return (
<div>
<h1>Hello {message}!</h1>
<a href="" onClick={this.toggle}>Toggle</a>
</div>
);
}
});
import connectToStores from 'alt/utils/connectToStores';
import TodoStore from './TodoStore';
import { Component } from 'react';
@connectToStores
class TodoView extends Component {
static getStores() {
return [TodoStore];
}
static getPropsFromStores() {
return TodoStore.getState();
}
render() {
return (
<ul>
{this.props.todos.map((todo) => {
return (
<li key={todo.id}>{todo.text}</li>
);
})}
</ul>
);
}
}
REACT focusES on two things:
Rendering the UI, from STATE
Responding to user inputs (events)
source: "Immutable Front-end in ClojureScript" by Logan Linn
write UI once, render a dynamic, data-driven (state) page that works for both initial creation and future updates—ideally, with no dom manipulation
Render
from scratch, every time
optionally, batch every requestAnimationFrame
How to render efficient Dom updates?
Instead of attempting to mutate a static DOM in—place, React effectively regenerates the DOM from scratch with every update to the application data.
Virtual Dom
Operating on the VDOM is quicker than operating on the browser’s native DOM
Generate HTML from plain Javascript data
Virtual Dom is opaque
state management
OR, manging state over time
(not that simple)
Who owns the state
Where does it flow
How does it change
Understanding your UI components
=
Understanding their life cycle
state
Identity and state are not equivalent things.
Object Oriented programming typically unifies identity and state.
this includes javascript
source: http://www.raywenderlich.com/109030/rwdevcon-inspiration-talk-identity-by-alexis-gallagher
An identity is an entity that has a state, which is its value at a point in time
state ui
‘Is this checkbox checked’
‘Did my ajax call succeed, and was I able to decode the JSON response and if yes, is the status field OK?’
The user usually clicks around, types things, gets angry at his wife and submits the form ten times and finds breakable parts of our UI that we never ever dreamed of.
UI IS A SNAPSHOT OF STATE, IN TIME
UI is build with components
that can share state ... over time
they can communicate!
The bigger your interface gets, the more likely it is to have one interaction that triggers an update, which in turn triggers another update, which in turn trigger many other components.
React delegates all events in the application to a single event handler (Dispatcher)
Batch strategies allow you to render at requestAnimationFrame
Top-down, value-based rendering allow component composition
Uni-directional flow
Components Events
model
STATE "AS" Data
views
components "AS" functions
controleRS
Communicating EVENTS
Instead of cascading changes on Dom => batch changes on Virtual Dom
Instead of inserting Js in => HTML Put Html in Js
Instead of communicating from child view to parent => communicate from Parent to Child views
Instead of a separate Markup, Code, Styles => Put them in one place
Simpler primitives
MORE experimentation
Good frameworks leverage the strengths of the language, not attempt to abstract them away.
Clojurescript^REACT
or how I stopPED worrying about react
Clojurescript fixes common patterns and issues
in React at the language level.
Clojurescript models state, identity, time, and ASYNC
in the language
=> State management and communication primitives, built-in
more Efficient diffs
overrides shouldComponentUpdate to a cheap identity check: cljs data-structures have persistent, immutable.
batched updates
on requestAnimationFrame
React doesn't currently use requestAnimationFrame to do DOM updates (as we call it, the "batching strategy"). The batching strategy is injectible though so it's possible to use something else. Om makes use of that possibility and uses requestAnimationFrame to batch DOM updates
- Facebook team
(def ^:private refresh-queued (atom #{}))
(def ^:private req-anim-frame
(if (exists? js/requestAnimationFrame)
js/requestAnimationFrame
(fn [f] (js/setTimeout f 16))))
(defn mount [element node]
(when-not (@refresh-queued node)
(swap! refresh-queued conj node)
(req-anim-frame (fn []
(swap! refresh-queued disj node)
(js/React.render element node)))))
source: https://github.com/weavejester/brutha
Global State management
atoms, cursors: OM, Js* libs
source: http://blog.getprismatic.com/content/images/2014/Jun/Cursor-example.png
Local State management
Cursors
Cursors allow components to selectively update themselves
Cursors propagate changes back to the original atom.
Each component gets a reference into the data store, and can update its own data using the cursor, transparently.
It isn't that components declare what data they need, but their parents provide them with a cursor when instantiating them.
Computed Observables
reagent
Automatic state propagation
Always reflect the latest state
Can depend on multiple atoms
Are themselves watchable
Mixins
RUM
Provides an API and set of building blocks to write components you need to.
Lower-level details are well-defined and open for extensions.
Uniform approach to manage application state.
Build your own storage model.
;; Regular static top-down component with immutable args
(rum/defc colored-clock < rum/static [time color]
[:span {:style {:color color}} (ts->str time)])
(rum/defc reactive-timer < rum/reactive []
[:div "Reactive: "
;; Subscribing to atom changes with rum/react
;; Then pass _values_ to static component
(colored-clock (rum/react clock) (rum/react color))])
;; After initial mount, all changes will be re-rendered automatically
(rum/mount (reactive-timer) (el "reactive-timer"))
;; Reactive drawing board
(def rboard (atom (mixins/random-board)))
(def rboard-renders (atom 0))
(rum/defc rcell < rum/reactive [x y]
(swap! rboard-renders inc)
(let [cursor (rum/cursor rboard [y x])]
;; each cell subscribes to its own cursor inside a board
;; only if cell is on (@cursor == true),
;; this component will be notified on color changes
[:div.art-cell {:style {:background-color (when (rum/react cursor) (rum/react color))}
:on-mouse-over (fn [_] (swap! cursor not) nil)}]))
(rum/defc art-rboard []
[:div.artboard
(for [y (range 0 utils/board-height)]
[:div.art-row {:key y}
(for [x (range 0 utils/board-width)]
;; this is how one can specify React key for component
(rum/with-props rcell x y :rum/key [x y]))])
(mixins/board-stats rboard rboard-renders)])
(rum/mount (art-rboard) (el "rboard"))
templates
JS: JSX
CLJs: sablono, kioo, flupot
HTML => CLJS => HTML
all HTML in CLJS
plain react DOM functions
BYO Template
data queries
datascripT
what if you your user-interface had a Database?
Every frontend app has an adhoc-state. Put it in a client-side in-memory database.
Whole DB Pre-fetch
Hierarchical, sparse, irregular, graph ...
Binary search, fast lookup, reverse iteration,
Clients become decoupled and independent: rendering, server sync
Always consistent render
source: https://dl.dropboxusercontent.com/u/561580/conferences/2015.07%20polyconf.pdf
(rum/defc filter-pane [db]
[:.filter-pane
[:input.filter {:type "text"
:value (or (system-attr db :system/filter) "")
:on-change (fn [_]
(set-system-attrs! :system/filter (dom/value (dom/q ".filter"))))
:placeholder "Filter"}]])
;; Rules are used to implement OR semantic of a filter
;; ?term must match either :project/name OR :todo/tags
(def filter-rule
'[[(match ?todo ?term)
[?todo :todo/project ?p]
[?p :project/name ?term]]
[(match ?todo ?term)
[?todo :todo/tags ?term]]])
;; terms are passed as a collection to query,
;; each term futher interpreted with OR semantic
(defn todos-by-filter [db terms]
(d/q '[:find [?e ...]
:in $ % [?term ...]
:where [?e :todo/text]
(match ?e ?term)]
db filter-rule terms))
Fetching data is hard, dealing with ever-changing data is hard, and performance is hard. Relay aims to reduce these problems to simple ones
Inspired by Flux
Instead of multiple stores, there is a central store caches all GraphQL data
Relay, GraphQL
OM.NEXT
A query engine to answer non-trivial questions about current app state.
Structured format to track data coming in and out of DB. Datalog queries can be run against it too.
wait, there's morE!
custom vdoms
in 328 lines of CLJS ... a prototype
~10 virtual Dom implementations in Js
remember
react is concerned with two things:
rendering STATE
communicating EVENTS
build your own "framework"
Building User Interfaces Should be fun again!
Thank you!
@priyatam
priyatam@facjure.com
https://slides.com/priyatam/react-life-without-mvc
References
http://facebook.github.io/react/
https://github.com/omcljs/om
https://github.com/reagent-project/reagent
https://github.com/tonsky/rum
https://github.com/tonsky/datascript
http://re-demo.s3-website-ap-southeast-2.amazonaws.com/
http://blog.getprismatic.com
a complete ui template in Cljs/Om:
https://github.com/priyatam/mala
React, Life without MVC
By Priyatam Mudivarti
React, Life without MVC
OO programming has many real benefits but one of its worst influences on UIs is obscuring Data. Yet, dozens of MVC frameworks continue to grow and introduce leaky abstractions with middlemen like Models, Views, Controllers and Directives. React is simple. Talk talk is a high-level overview on React's core principles, with an emphasis on Clojurescript’s abstractions like immutable data, state, and transitions.
- 1,637