Upgrade to Pro — share decks privately, control downloads, hide ads and more …

flux-react

Bill Fisher
October 20, 2014

 flux-react

Overview of how and why to build a React + Flux application.

Bill Fisher

October 20, 2014
Tweet

More Decks by Bill Fisher

Other Decks in Programming

Transcript

  1. React and Flux
    Building Applications with a Unidirectional Data Flow

    View full-size slide

  2. React and Flux
    Building Applications with a Unidirectional Data Flow
    Bill Fisher
    @fisherwebdev

    View full-size slide

  3. React and Flux
    Bill Fisher
    @fisherwebdev
    https://speakerdeck.com/fisherwebdev/flux-react
    http://facebook.github.io/flux/
    http://facebook.github.io/react/

    View full-size slide

  4. Bill Fisher
    @fisherwebdev
    #reactjs #fluxjs
    https://speakerdeck.com/fisherwebdev/flux-react
    http://facebook.github.io/flux/
    http://facebook.github.io/react/

    View full-size slide

  5. https://speakerdeck.com/fisherwebdev/flux-react

    View full-size slide

  6. https://speakerdeck.com/fisherwebdev/flux-react

    View full-size slide

  7. Model
    Model
    Model
    Model

    View full-size slide

  8. Model
    Model
    Model
    Model

    View full-size slide

  9. Model
    Model
    Model
    Model

    View full-size slide

  10. Model
    Model
    Model
    Model

    View full-size slide

  11. Model
    Model
    Model
    Model

    View full-size slide

  12. Dispatcher
    Action
    Store
    View

    View full-size slide

  13. THE PLAN
    Anatomy of a React + Flux Application
    Actions & Action Creators
    The Dispatcher
    Stores + Testing
    Views, Controller-views and React
    Initialization & Interacting with a Web API

    View full-size slide

  14. FILES
    Primary:
    action creators, dispatcher, stores, React components
    Secondary:
    bootstrap, utilities, constants

    View full-size slide

  15. .  
    ├──  css  
    │      └──  app.css  
    ├──  index.html  
    ├──  js  
    │      ├──  actions  
    │      │      └──  FooActionCreators.js  
    │      ├──  AppBootstrap.js  
    │      ├──  AppConstants.js  
    │      ├──  AppDispatcher.js  
    │      ├──  stores  
    │      │      ├──  FooStore.js  
    │      │      └──  __tests__  
    │      │              └──  FooStore-­‐test.js  
    │      ├──  utils  
    │      │      ├──  AppWebAPIUtils.js  
    │      │      ├──  FooUtils.js  
    │      │      └──  __tests__  
    │      │              ├──  AppWebAPIUtils-­‐test.js  
    │      │              └──  FooUtils-­‐test.js  
    │      └──  views  
    │              ├──  FooControllerView.react.js  
    │              └──  FooChild.react.js  
    └──  package.json

    View full-size slide

  16. js  
    ├──  actions  
    │      └──  FooActionCreators.js  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  AppDispatcher.js  
    ├──  stores  
    │      ├──  FooStore.js  
    │      └──  __tests__  
    │              └──  FooStore-­‐test.js  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──  views  
           ├──  FooControllerView.react.js  
           └──  FooChild.react.js
    .  
    ├──  css  
    │      └──  app.css  
    ├──  index.html  
    ├──  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    │  
    └──  package.json

    View full-size slide

  17. js  
    ├──  actions  
    │      └──  FooActionCreators.js  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  AppDispatcher.js  
    ├──  stores  
    │      ├──  FooStore.js  
    │      └──  __tests__  
    │              └──  FooStore-­‐test.js  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──  views  
           ├──  FooControllerView.react.js  
           └──  FooChild.react.js

    View full-size slide

  18. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js

    View full-size slide

  19. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  20. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  21. actions  
    └──  FooActionCreators.js
    Action
    ACTIONS & ACTION CREATORS

    View full-size slide

  22. ACTIONS & ACTION CREATORS
    Actions:
    an object with a type property and new data
    Action creators:
    semantic methods that create actions
    collected together in a module to become an API

    View full-size slide

  23. //  FooActionCreators.js  
    !
    var  AppDispatcher  =  require('../AppDispatcher');  
    var  AppConstants  =  require('../AppConstants');  
    !
    var  ActionTypes  =  Constants.ActionTypes;  
    !
    module.exports  =  {  
    !
       createMessage:  function(text)  {  
           AppDispatcher.dispatch({  
               type:  ActionTypes.MESSAGE_CREATE,  
               text:  text  
           });  
       }  
    !
    };

    View full-size slide

  24. !
       createMessage:  function(text)  {  
           AppDispatcher.dispatch({  
               type:  ActionTypes.MESSAGE_CREATE,  
               text:  text  
           });  
       }

    View full-size slide

  25. !
       createMessage:  function(text)  {  
           AppDispatcher.dispatch({  
               type:  ActionTypes.MESSAGE_CREATE,  
               text:  text  
           });  
       }

    View full-size slide

  26. !
       createMessage:  function(text)  {  
           AppDispatcher.dispatch({  
               type:  ActionTypes.MESSAGE_CREATE,  
               text:  text  
           });  
       }

    View full-size slide

  27. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  28. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  29. THE DISPATCHER
    AppDispatcher.js
    Dispatcher

    View full-size slide

  30. THE DISPATCHER
    Essentially a registry of callbacks
    To dispatch, it invokes all the callbacks with a payload
    Flux dispatcher is a singleton; payload is an action
    Primary API: dispatch(), register(), waitFor()
    Base class is available through npm or Bower.

    View full-size slide

  31. //  AppDispatcher.js  
    !
    var  Dispatcher  =  require('Flux.Dispatcher');  
    !
    //  export  singleton  
    module.exports  =  new  Dispatcher();  

    View full-size slide

  32. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  33. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  34. Store
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    STORES

    View full-size slide

  35. STORES
    Each store is a singleton
    The locus of control within the application
    Manages application state for a logical domain
    Private variables hold the application data
    Numerous collections or values can be held in a store

    View full-size slide

  36. STORES
    Register a callback with the dispatcher
    Callback is the only way data gets into the store
    No setters, only getters: a universe unto itself
    Emits an event when state has changed

    View full-size slide

  37. //  FooStore.js  
    !
    var  _dispatchToken;  
    var  _messages  =  {};  
    !
    class  FooStore  extends  EventEmitter  {  
    !
       constructor()  {  
           super();  
           _dispatchToken  =  AppDispatcher.register((action)  =>  {  
    !
               switch(action.type)  {  
    !
                   case  ActionTypes.MESSAGE_CREATE:  
                       var  message  =  {  
                           id:  Date.now(),  
                           text:  action.text  
                       }  
                       _messages[message.id]  =  message;  
                       this.emit('change');  
                       break;  
    !
                   case  ActionTypes.MESSAGE_DELETE:  
                       delete  _messages[action.messageID];  
                       this.emit('change');  
                       break;  
    !
                   default:  
                       //  no  op  
               }  
    !
           });  
       }  
    !
       getDispatchToken()  {  
           return  _dispatchToken;  
       }  
    !
       getMessages()  {  
           return  _messages;  
       }  
    !
    }  
    !
    module.exports  =  new  FooStore();  
           _dispatchToken  =  AppDispatcher.register((action)  =>  {  
    !
               switch(action.type)  {  
    !
                   case  ActionTypes.MESSAGE_CREATE:  
                       var  message  =  {  
                           id:  Date.now(),  
                           text:  action.text  
                       }  
                       _messages[message.id]  =  message;  
                       this.emit('change');  
                       break;  
    !
                   case  ActionTypes.MESSAGE_DELETE:  
                       delete  _messages[action.messageID];  
                       this.emit('change');  
                       break;  
    !
                   default:  
                       //  no  op  
               }  
    !
           });

    View full-size slide

  38.        _dispatchToken  =  AppDispatcher.register((action)  =>  {  
    !
               switch(action.type)  {  
    !
                   case  ActionTypes.MESSAGE_CREATE:  
                       var  message  =  {  
                           id:  Date.now(),  
                           text:  action.text  
                       }  
                       _messages[message.id]  =  message;  
                       this.emit('change');  
                       break;  
    !
                   case  ActionTypes.MESSAGE_DELETE:  
                       delete  _messages[action.messageID];  
                       this.emit('change');  
                       break;  
    !
                   default:  
                       //  no  op  
               }  
    !
           });

    View full-size slide

  39. TESTING STORES WITH JEST
    Stores have no setters — how to test is not obvious
    Jest is Facebook’s auto-mocking test framework
    Built on top of Jasmine

    View full-size slide

  40. callback  =  AppDispatcher.register.mock.calls[0][0];  

    View full-size slide

  41. jest.dontMock('FooStore');  
    !
    var  AppConstants  =  require('AppConstants');  
    !
    var  ActionTypes  =  AppConstants.ActionTypes;  
    !
    describe('FooStore',  function()  {  
    !
       var  callback;  
    !
       beforeEach(function()  {  
           AppDispatcher  =  require('AppDispatcher');  
           FooStore  =  require('FooStore');  
           callback  =  AppDispatcher.register.mock.calls[0][0];  
       });  
    !
       it('can  create  messages',  function()  {  
           callback({  
               type:  ActionTypes.MESSAGE_CREATE,  
               text:  'test'  
           });  
           var  messages  =  FooStore.getMessages();  
           var  firstKey  =  Objects.keys(messages)[0];  
           expect(FooStore.getMessages()[firstKey].text).toBe(‘test’);  
       });  
    !
    });
    callback  =  AppDispatcher.register.mock.calls[0][0];  

    View full-size slide

  42. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  43. js  
    ├──  
    │  
    ├──  AppBootstrap.js  
    ├──  AppConstants.js  
    ├──  
    ├──  
    │  
    │  
    │  
    ├──  utils  
    │      ├──  AppWebAPIUtils.js  
    │      ├──  FooUtils.js  
    │      └──  __tests__  
    │              ├──  AppWebAPIUtils-­‐test.js  
    │              └──  FooUtils-­‐test.js  
    └──    
    !
    actions  
    └──  FooActionCreators.js
    AppDispatcher.js
    stores  
    ├──  FooStore.js  
    └──  __tests__  
           └──  FooStore-­‐test.js
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    Dispatcher
    Action
    Store
    View

    View full-size slide

  44. View
    views  
    ├──  FooControllerView.react.js  
    └──  FooChild.react.js
    VIEWS & CONTROLLER VIEWS

    View full-size slide

  45. VIEWS & CONTROLLER VIEWS
    Tree of React components
    Controller views are near the root, listen for change events
    On change, controller views query stores for new data
    With new data, they re-render themselves & children

    View full-size slide

  46. REACT AND THE DOM
    Component-based system for managing DOM updates
    Uses a “Virtual DOM”: data structure and algorithm
    Updates the DOM as efficiently as possible
    Huge performance boost
    Bonus: we can stop thinking about managing the DOM

    View full-size slide

  47. REACT’S PARADIGM
    Based on Functional-Reactive principles
    Unidirectional data flow
    Composability
    Predictable, Reliable, Testable
    Declarative: what the UI should look like, given props

    View full-size slide

  48. USING REACT
    Data is provided through props
    Rendering is a function of this.props and this.state
    “Re-render” (or not) on every state change
    Keep components as stateless as possible
    Component lifecycle and update cycle methods

    View full-size slide

  49. getDefaultProps()  
    getInitialState()  
    componentWillMount()  
    render()
    componentWillReceiveProps()*  
    shouldComponentUpdate()  
    componentWillUpdate()  
    render()
    DOM  Mutations  Complete
    componentDidMount() componentDidUpdate()
    componentWillUnmount()
    Lifecycle:    
    Mounting  and  Unmounting
    Update:    
    New  Props  or  State
    *Called  only  with  new  props

    View full-size slide

  50. getDefaultProps()  
    getInitialState()  
    componentWillMount()  
    render()
    componentWillReceiveProps()*  
    shouldComponentUpdate()  
    componentWillUpdate()  
    render()
    DOM  Mutations  Complete
    componentDidMount() componentDidUpdate()
    componentWillUnmount()
    Lifecycle:    
    Mounting  and  Unmounting
    Update:    
    New  Props  or  State
    *Called  only  with  new  props

    View full-size slide

  51. !
    !
    var  React  =  require('react');  
    !
    !
    !
    !
    !
    !
    !
    !
    !
    module.exports  =  FooControllerView;  
    //  FooControllerView.react.js  
    var  FooControllerView  =  React.createClass({  
    !
       render:  function()  {  
           //  TODO  
       }  
    !
    });

    View full-size slide

  52. //  FooControllerView.react.js  
    var  FooControllerView  =  React.createClass({  
    !
       render:  function()  {  
           //  TODO  
       }  
    !
    });

    View full-size slide

  53. //  FooControllerView.react.js  
    var  FooControllerView  =  React.createClass({  
    !
       componentDidMount:  function()  {  
           FooStore.on('change',  this._onChange);  
       },  
    !
       componentWillUnmount:  function()  {  
           FooStore.removeListener('change',  this._onChange);  
       },  
    !
       render:  function()  {  
           //  TODO  
       }  
    !
    });

    View full-size slide

  54. //  FooControllerView.react.js  
    var  FooControllerView  =  React.createClass({  
    !
       getInitialState:  function()  {  
           return  {  messages:  FooStore.getMessages()  };  
       },  
    !
       componentDidMount:  function()  {  
           FooStore.on('change',  this._onChange);  
       },  
    !
       componentWillUnmount:  function()  {  
           FooStore.removeListener('change',  this._onChange);  
       },  
    !
       render:  function()  {  
           //  TODO  
       }  
    !
       _onChange:  function()  {  
           this.setState({  messages:  FooStore.getMessages()  });  
       },  
    !
    });

    View full-size slide

  55. var  FooControllerView  =  React.createClass({  
    !
       getInitialState:  function()  {  
           return  {  messages:  FooStore.getMessages()  };  
       },  
    !
       componentDidMount:  function()  {  
           FooStore.on('change',  this._onChange);  
       },  
    !
       componentWillUnmount:  function()  {  
           FooStore.removeListener('change',  this._onChange);  
       },  
    !
         
         
         
    !
       _onChange:  function()  {  
           this.setState({  messages:  FooStore.getMessages()  });  
       },  
    !
    });
    //  FooControllerView.react.js  
       render:  function()  {  
           //  TODO  
       }

    View full-size slide

  56.    render:  function()  {  
           //  TODO  
       }

    View full-size slide

  57. render:  function()  {  
       return  (  
             
             
       );  
    },

    View full-size slide

  58. render:  function()  {  
       var  messageListItems  =  [];  
       return  (  
             
               {messageListItems}  
             
       );  
    },

    View full-size slide

  59. render:  function()  {  
       var  messageListItems  =  this.state.messages.map(message  =>  {  
           return  (  
                               key={message.id}  
                   id={message.id}  
                   text={message.text}  
               />  
           );  
       });  
       return  (  
             
               {messageListItems}  
             
       );  
    },

    View full-size slide

  60. //  FooChild.react.js  
    !
    var  React  =  require('react');  
    !
    var  FooChild  =  React.createClass({  
    !
       propTypes  =  {  
           id:  React.PropTypes.number.isRequired,  
           text:  React.PropTypes.string.isRequired  
       }  
    !
       render:  function()  {  
           return  (  
               {this.props.text}  
           );  
       }  
    !
    });  
    !
    module.exports  =  FooChild;  

    View full-size slide

  61. INITIALIZATION OF THE APP
    Usually done in a bootstrap module
    Initializes stores with an action
    Renders the topmost React component

    View full-size slide

  62. CALLING A WEB API
    Use a WebAPIUtils module to encapsulate XHR work.
    Start requests directly in the Action Creators, or in the stores.
    Important: create a new action on success/error.
    Data must enter the system through an action.

    View full-size slide

  63. FLUX
    Dataflow
    Programming CQRS
    Functional and FRP
    MVC
    Reactive
    WHERE DOES FLUX FIT IN

    View full-size slide

  64. FLUX
    TOTALLY
    WORKS
    Dispatcher
    Action
    Store
    View

    View full-size slide