flux-react

83428d816d84e071104956e9c7726305?s=47 Bill Fisher
October 20, 2014

 flux-react

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

83428d816d84e071104956e9c7726305?s=128

Bill Fisher

October 20, 2014
Tweet

Transcript

  1. 12.
  2. 13.
  3. 14.
  4. 15.
  5. 16.
  6. 18.

    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
  7. 20.

    .   ├──  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
  8. 21.

    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
  9. 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
  10. 23.

    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
  11. 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 Dispatcher Action Store View
  12. 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
  13. 27.

    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
  14. 28.

    //  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          });      }   ! };
  15. 29.

    !    createMessage:  function(text)  {          AppDispatcher.dispatch({

                 type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }
  16. 30.

    !    createMessage:  function(text)  {          AppDispatcher.dispatch({

                 type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }
  17. 31.

    !    createMessage:  function(text)  {          AppDispatcher.dispatch({

                 type:  ActionTypes.MESSAGE_CREATE,              text:  text          });      }
  18. 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
  19. 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
  20. 35.

    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.
  21. 36.

    //  AppDispatcher.js   ! var  Dispatcher  =  require('Flux.Dispatcher');   !

    //  export  singleton   module.exports  =  new  Dispatcher();  
  22. 37.

    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
  23. 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
  24. 39.

    Store stores   ├──  FooStore.js   └──  __tests__    

         └──  FooStore-­‐test.js STORES
  25. 40.

    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
  26. 41.

    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
  27. 42.

    //  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              }   !        });
  28. 43.

           _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              }   !        });
  29. 44.

    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
  30. 46.

    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];  
  31. 47.

    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
  32. 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
  33. 50.

    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
  34. 51.
  35. 52.

    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
  36. 53.

    REACT’S PARADIGM Based on Functional-Reactive principles Unidirectional data flow Composability

    Predictable, Reliable, Testable Declarative: what the UI should look like, given props
  37. 54.

    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
  38. 55.

    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
  39. 56.

    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
  40. 57.

    ! ! var  React  =  require('react');   ! ! !

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

    //  FooControllerView.react.js   var  FooControllerView  =  React.createClass({   !  

     render:  function()  {          //  TODO      }   ! });
  42. 59.

    //  FooControllerView.react.js   var  FooControllerView  =  React.createClass({   !  

     componentDidMount:  function()  {          FooStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          FooStore.removeListener('change',  this._onChange);      },   !    render:  function()  {          //  TODO      }   ! });
  43. 60.

    //  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()  });      },   ! });
  44. 61.

    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      }
  45. 63.

    render:  function()  {      return  (      

       <ul>          </ul>      );   },
  46. 64.

    render:  function()  {      var  messageListItems  =  [];  

       return  (          <ul>              {messageListItems}          </ul>      );   },
  47. 65.

    render:  function()  {      var  messageListItems  =  this.state.messages.map(message  =>

     {          return  (              <FooChild                  key={message.id}                  id={message.id}                  text={message.text}              />          );      });      return  (          <ul>              {messageListItems}          </ul>      );   },
  48. 66.

    //  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;  
  49. 67.

    INITIALIZATION OF THE APP Usually done in a bootstrap module

    Initializes stores with an action Renders the topmost React component
  50. 68.

    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.