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

flux-meetup

83428d816d84e071104956e9c7726305?s=47 Bill Fisher
November 07, 2014

 flux-meetup

Talk given to the ReactJS Meetup

83428d816d84e071104956e9c7726305?s=128

Bill Fisher

November 07, 2014
Tweet

Transcript

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

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

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

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

  5. https://speakerdeck.com/fisherwebdev/flux-meetup "Couch" by Brian Teutsch, used under CC BY 2.0

    / Modified from original
  6. "Couch" by Brian Teutsch, used under CC BY 2.0 /

    Modified from original https://speakerdeck.com/fisherwebdev/flux-meetup
  7. Model Model Model Model

  8. Model Model Model Model

  9. Model Model Model Model

  10. Model Model Model Model

  11. Model Model Model Model

  12. None
  13. None
  14. None
  15. "Tangled wires, Freegeek, Portland, Oregon, USA" by Cory Doctorow, used

    under CC BY 2.0\
  16. "Spaghetti? Yum!" by Dan McKay, used under CC BY 2.0

  17. Dispatcher Action Store View

  18. "Couch" by Brian Teutsch, used under CC BY 2.0 /

    Modified from original
  19. 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, Immutable Data
  20. FILES Primary: action creators, dispatcher, stores, React components Secondary: bootstrap,

    utilities, constants
  21. .   ├──  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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. actions   └──  FooActionCreators.js Action ACTIONS & ACTION CREATORS

  28. 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
  29. //  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          });      }   ! };
  30. !    createMessage:  function(text)  {          AppDispatcher.dispatch({

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

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

                 type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }
  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
  34. 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
  35. THE DISPATCHER AppDispatcher.js Dispatcher

  36. 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.
  37. //  AppDispatcher.js   ! var  Dispatcher  =  require('Flux.Dispatcher');   !

    //  export  singleton   module.exports  =  new  Dispatcher();  
  38. 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
  39. 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
  40. Store stores   ├──  FooStore.js   └──  __tests__    

         └──  FooStore-­‐test.js STORES
  41. 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
  42. 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
  43. //  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              }   !        });
  44.        _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              }   !        });
  45. 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
  46. callback  =  AppDispatcher.register.mock.calls[0][0];  

  47. 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];  
  48. 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
  49. 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
  50. View views   ├──  FooControllerView.react.js   └──  FooChild.react.js VIEWS &

    CONTROLLER VIEWS
  51. 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
  52. 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
  53. 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
  54. ! ! var  React  =  require('react');   ! ! !

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

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

     componentDidMount:  function()  {          FooStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          FooStore.removeListener('change',  this._onChange);      },   !    render:  function()  {          //  TODO      }   ! });
  57. //  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()  });      },   ! });
  58. 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      }
  59. render:  function()  {      var  messageListItems  =  this.state.messages.map(message  =>

     {          return  (              <FooChild                  key={message.id}                  id={message.id}                  text={message.text}              />          );      });      return  (          <ul>              {messageListItems}          </ul>      );   },
  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  (              <li>{this.props.text}</li>          );      }   ! });   ! module.exports  =  FooChild;  
  61. INITIALIZATION OF THE APP Usually done in a bootstrap module

    Initializes stores with an action Renders the topmost React component
  62. module.exports  =  function(/*object*/  data,  /*DOMElement*/  elem)  {   !  

     var  dispatchToken  =  AppDispatcher.register(action  =>  {          if  (action.type  !==  Constants.ActionTypes.INITIAL_LOAD)  {              return;          }   !        //  wait  for  all  the  stores  to  initialize  before  rendering          var  tokens  =  [              ContentStore,              FriendStore,              LoggingStore,              ThemeStore,          ].map(store  =>  store.getDispatchToken());          AppDispatcher.waitFor(tokens);   !        React.render(<AppRoot  />,  elem);   !        AppDispatcher.unregister(dispatchToken);      });            Actions.initialize(data);  //  Creates  INITIAL_LOAD  action   };
  63. 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.
  64. IMMUTABLE DATA Boost performance in React’s shouldComponentUpdate() React.addons.PureRenderMixin immutable-js: http://facebook.github.io/immutable-js/

  65. FLUX Dataflow Programming CQRS Functional and FRP MVC Reactive WHERE

    DOES FLUX FIT IN
  66. FLUX TOTALLY WORKS Dispatcher Action Store View

  67. THANK YOU!