React, Transparent Reactive Programming and Mutable data structures

React, Transparent Reactive Programming and Mutable data structures

The ability to express essential complexity in a simple way is crucial for any code-base. At Mendix we did an interesting discovery during the development of a complex MDD tool. React, mutable data structures and transparent reactive programming are a match made in heaven. We published a library that leverages these concepts; MobX. It helps you to write simple, declarative, yet highly efficient code. Your future code maintainers will love you for applying it.

Bdbeb02a7fe50b769e67e0c076b33c54?s=128

Michel Weststrate

November 04, 2015
Tweet

Transcript

  1. DON'T BUILD APPS. BUILD SPREADSHEETS. Reactive programming and mutable data

    structures in large applications Michel Weststrate @mweststrate
  2. What do I think about JQuery? JQuery

  3. SOFTWARE TO BUILD ENTERPRISE SOFTWARE

  4. 479 Fully editable domain concepts

  5. How would you build this? Our solution is simpler (and

    faster)
  6. ❏ React contexts ❏ Store / data subscriptions ❏ Lenses

    ❏ Smart components ❏ Stateful components ❏ Immutable State ❏ Denormalized data
  7. Data Cells Formulas, Charts Data Entry / Import mutates formula

    (observe) Library Stars React 30,588 Angular 43,808 MobX 1,418
  8. State Views boxes arrows Canvas BoxView BoxView ArrowView ArrowView Action

    mutate view function (observe) invoke
  9. 1. Observable State

  10. 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
  11. 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
  12. 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(); } }
  13. 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
  14. 1. Non-normalized data 2. Forgotten data subscriptions

  15. 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
  16. Automatically pushing changes through the system

  17. 2. Reactive Views

  18. class Canvas extends Component { render() { const {store} =

    this.props; return ( <div className="app"> <div className="canvas"> <svg> { store.arrows.map(arrow => <ArrowView arrow={arrow} key={arrow.id} /> ) } </svg> { store.boxes.map(box => <BoxView box={box} store={store} key={box.id} /> ) } </div> <Sidebar store={store} /> </div> ); } } arrows boxes
  19. 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 <path className="arrow" d={`M${x1} ${y1} L${x2} ${y2}`} />; } } @observer boxes box props ?! that's all
  20. @observer class Canvas extends Component { render() { const {store}

    = this.props; return ( <div className="app"> <div className="canvas"> <svg> { store.arrows.map(arrow => <ArrowView arrow={arrow} key={arrow.id} /> ) } </svg> { store.boxes.map(box => <BoxView box={box} store={store} key={box.id} /> ) } </div> <Sidebar store={store} /> </div> ); } }
  21. @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
  22. DOM comp. tree state graph patches patches

  23. 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 <path className="arrow" d={`M${x1} ${y1} L${x2} ${y2}`} />; }); stateless function components
  24. 3. Actions

  25. @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
  26. 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; } }
  27. 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; };
  28. Actions ❏ Straightforward data mutations ❏ No contextual awareness required

    ❏ No boilerplate
  29. What about.. ❏ Time Traveling? ❏ Hot Reloading?

  30. const states = []; autorun(() => { states.push(serializeState(store)); }); reads

    all data tracks all reads, fires on each write
  31. MobX core API (@)observable Makes stuff observable autorun Creates a

    self-updating function that monitors observable data @observer Wraps react component render in autorun
  32. 4. How does it work?

  33. 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
  34. 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
  35. 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
  36. @observer class BoxView extends Component { render() { const {box}

    = this.props; return (<DraggableCore onDrag={this.handleDrag}> <div style={{ width: box.width, left: box.x, top: box.y }}> {box.name} </div> </DraggableCore>); } handleDrag = (e, dragInfo) => { this.props.box.x += dragInfo.position.deltaX; this.props.box.y += dragInfo.position.deltaY; } }
  37. 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?