Slide 1

Slide 1 text

DON'T BUILD APPS. BUILD SPREADSHEETS. Reactive programming and mutable data structures in large applications Michel Weststrate @mweststrate

Slide 2

Slide 2 text

What do I think about JQuery? JQuery

Slide 3

Slide 3 text

SOFTWARE TO BUILD ENTERPRISE SOFTWARE

Slide 4

Slide 4 text

479 Fully editable domain concepts

Slide 5

Slide 5 text

How would you build this? Our solution is simpler (and faster)

Slide 6

Slide 6 text

❏ React contexts ❏ Store / data subscriptions ❏ Lenses ❏ Smart components ❏ Stateful components ❏ Immutable State ❏ Denormalized data

Slide 7

Slide 7 text

Data Cells Formulas, Charts Data Entry / Import mutates formula (observe) Library Stars React 30,588 Angular 43,808 MobX 1,418

Slide 8

Slide 8 text

State Views boxes arrows Canvas BoxView BoxView ArrowView ArrowView Action mutate view function (observe) invoke

Slide 9

Slide 9 text

1. Observable State

Slide 10

Slide 10 text

const store = { boxes: [], arrows: [], selection: null }; store.boxes.push( new Box("Rotterdam", 100, 100), new Box("Vienna", 700, 150) ); store.arrows.push({ id: randomUuid(), from: store.boxes[ 0], to: store.boxes[ 1] }); classes plain objects arrays references boxes arrows

Slide 11

Slide 11 text

const store = observable({ boxes: [], arrows: [], selection: null }); store.boxes.push( new Box("Rotterdam", 100, 100), new Box("Vienna", 700, 150) ); store.arrows.push({ id: randomUuid(), from: store.boxes[ 0], to: store.boxes[ 1] }); boxes arrows make stuff recursively observable

Slide 12

Slide 12 text

class Box { id = randomUuid(); @observable name = "A box"; @observable x = 0; @observable y = 0; @observable get width() { return this.name.length * 15; } constructor(name, x, y, id) { this.name = name; this.x = x; this.y = y; this.id = id || randomUuid(); } }

Slide 13

Slide 13 text

Immutables Data Structures ❏ Never change ❏ Structural equality ❏ Easy to memoize, structural sharing, no defensive copies, time travelling ❏ Assumes state is a tree, not a graph ❏ Denormalize the rest boxes arrows

Slide 14

Slide 14 text

1. Non-normalized data 2. Forgotten data subscriptions

Slide 15

Slide 15 text

Observable Mutable Data Structures ❏ Identity equality ❏ Concept should exists only once in memory ❏ Always up-to-date ❏ Composition & associations ❏ Prototypes and type checking ❏ Natural mental model ❏ Simple actions boxes arrows

Slide 16

Slide 16 text

Automatically pushing changes through the system

Slide 17

Slide 17 text

2. Reactive Views

Slide 18

Slide 18 text

class Canvas extends Component { render() { const {store} = this.props; return (
{ store.arrows.map(arrow => ) } { store.boxes.map(box => ) }
); } } arrows boxes

Slide 19

Slide 19 text

class ArrowView extends Component { render() { const {from, to} = this.props.arrow; const [x1, y1, x2, y2] = [ from.x + from.width / 2, from.y + 30, to.x + to.width / 2, to.y + 30 ]; return ; } } @observer boxes box props ?! that's all

Slide 20

Slide 20 text

@observer class Canvas extends Component { render() { const {store} = this.props; return (
{ store.arrows.map(arrow => ) } { store.boxes.map(box => ) }
); } }

Slide 21

Slide 21 text

@observer ❏ mobx.autorun(() => this.render()) ❏ autorun ❏ Takes a function, makes it reactive: ❏ After each run: subscribe to all data accessed while running ❏ Re-run on data changes ❏ Optimizes dependency tree ❏ PureRenderMixin

Slide 22

Slide 22 text

DOM comp. tree state graph patches patches

Slide 23

Slide 23 text

const ArrowView = observer(props => { const {from, to} = props.arrow; const [x1, y1, x2, y2] = [ from.x + from.width / 2, from.y + 62, to.x + to.width / 2, to.y ]; return ; }); stateless function components

Slide 24

Slide 24 text

3. Actions

Slide 25

Slide 25 text

@observer class Sidebar extends Component { render() { const {selection} = this.props.store; return selection !== null ? < div className="sidebar sidebar-open" > < input onChange={this.onChange} value={selection.name} /> div> : < div className="sidebar" />; } onChange = (e) => { } } onChange = (e) => { this.props.store.selection.name = e.target.value; }; } just references updates all 'name' observers

Slide 26

Slide 26 text

onCanvasMouseUp = (e) => { const {store} = this.props; if (e.button === LEFT_BUTTON) { store.selection = null; } else if (e.button === RIGHT_BUTTON) { const newBox = store.addBox( "Hi.", e.clientX - 50, e.clientY - 20, store.selection ); store.selection = newBox; } }

Slide 27

Slide 27 text

store.addBox = function(name, x, y, fromBox ) { const newBox = new Box(name, x, y); this.boxes.push(newBox); if (fromBox) { this.arrows.push({ id : randomUuid(), from: fromBox, to: newBox }); } return newBox; };

Slide 28

Slide 28 text

Actions ❏ Straightforward data mutations ❏ No contextual awareness required ❏ No boilerplate

Slide 29

Slide 29 text

What about.. ❏ Time Traveling? ❏ Hot Reloading?

Slide 30

Slide 30 text

const states = []; autorun(() => { states.push(serializeState(store)); }); reads all data tracks all reads, fires on each write

Slide 31

Slide 31 text

MobX core API (@)observable Makes stuff observable autorun Creates a self-updating function that monitors observable data @observer Wraps react component render in autorun

Slide 32

Slide 32 text

4. How does it work?

Slide 33

Slide 33 text

Transparent reactive programming 1. Decorate all objects properties and arrays indices 2. Store running views in a stack 3. Getters register observers 4. Setters notify observers autorun Function Stack x * 2 get x x x set x

Slide 34

Slide 34 text

Dependency tree analysis 1. Subscription change over time! 2. Synchronous updates + transactions 3. Atomic updates 4. Cycle detection 5. Non-pure view function detection 6. Lazy / eager evaluation 7. Garbage collection

Slide 35

Slide 35 text

The most fluid parts of your application should be the simplest to build github.com/mobxjs/mobx feel free to more ask on gitter! in production

Slide 36

Slide 36 text

@observer class BoxView extends Component { render() { const {box} = this.props; return (
{box.name}
); } handleDrag = (e, dragInfo) => { this.props.box.x += dragInfo.position.deltaX; this.props.box.y += dragInfo.position.deltaY; } }

Slide 37

Slide 37 text

Questions? For example: 1. What about garbage collection? 2. Does it evaluate eagerly or lazily? 3. What about async actions? 4. Why not RxJS? 5. It updates synchronously, isn't that expensive? 6. What about flux?