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

flux-applicative

Avatar for Bill Fisher Bill Fisher
February 27, 2015

 flux-applicative

Avatar for Bill Fisher

Bill Fisher

February 27, 2015
Tweet

More Decks by Bill Fisher

Other Decks in Technology

Transcript

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

    Modified from original https://speakerdeck.com/fisherwebdev/flux-applicative
  2. "Couch" by Brian Teutsch, used under CC BY 2.0 /

    Modified from original https://speakerdeck.com/fisherwebdev/flux-applicative
  3. 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, Patterns & Anti-Patterns
  4. js   ├──   │   ├──  AppBootstrap.js   ├──

     AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  MessageActionCreators.js AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js
  5. js   ├──   │   ├──  AppBootstrap.js   ├──

     AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  MessageActionCreators.js AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js Dispatcher Action Store View
  6. js   ├──   │   ├──  AppBootstrap.js   ├──

     AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  MessageActionCreators.js AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js Dispatcher Action Store View
  7. 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
  8. //  MessageActionCreators.js   ! var  AppDispatcher  =  require('../AppDispatcher');   var

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

                 type:  ActionTypes.MESSAGE_CREATE,              text          });      }
  10.    createMessage:  text  =>  {          AppDispatcher.dispatch({

                 type:  ActionTypes.MESSAGE_CREATE,              text          });      }
  11.    createMessage:  text  =>  {          AppDispatcher.dispatch({

                 type:  ActionTypes.MESSAGE_CREATE,              text          });      }
  12. Dispatcher Action Store View js   ├──   │  

    ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  MessageActionCreators.js AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js
  13. js   ├──   │   ├──  AppBootstrap.js   ├──

     AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! AppDispatcher.js Dispatcher Action Store View stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js actions   └──  MessageActionCreators.js
  14. 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.
  15. //  AppDispatcher.js   ! var  Dispatcher  =  require('Flux.Dispatcher');   !

    //  export  singleton   module.exports  =  new  Dispatcher();
  16. Dispatcher Action Store View js   ├──   │  

    ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  MessageActionCreators.js AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js
  17. js   ├──   │   ├──  AppBootstrap.js   ├──

     AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js Dispatcher Action Store View actions   └──  MessageActionCreators.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js
  18. Store stores   ├──  MessageStore.js   └──  __tests__    

         └──  MessageStore-­‐test.js STORES
  19. 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
  20. 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
  21. //  MessageStore.js   ! var  _dispatchToken;   var  _messages  =

     {};   ! class  MessageStore  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  MessageStore();          _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              }   !        });  
  22.        _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              }   !        });  
  23. 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 http://facebook.github.io/jest/
  24. jest.dontMock('MessageStore');   ! var  AppConstants  =  require('AppConstants');   ! var

     ActionTypes  =  AppConstants.ActionTypes;   ! describe('MessageStore',  function()  {   !    var  callback;   !    beforeEach(function()  {          AppDispatcher  =  require('AppDispatcher');          MessageStore  =  require('MessageStore');          callback  =  AppDispatcher.register.mock.calls[0][0];      });   !    it('can  create  messages',  function()  {          callback({              type:  ActionTypes.MESSAGE_CREATE,              text:  'test'          });          var  messages  =  MessageStore.getMessages();          var  firstKey  =  Objects.keys(messages)[0];          expect(MessageStore.getMessages()[firstKey].text).toBe('test');      });   ! });   callback  =  AppDispatcher.register.mock.calls[0][0];  
  25. Dispatcher Action Store View js   ├──   │  

    ├──  AppBootstrap.js   ├──  AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! actions   └──  MessageActionCreators.js AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js
  26. js   ├──   │   ├──  AppBootstrap.js   ├──

     AppConstants.js   ├──   ├──   │   │   │   ├──  utils   │      ├──  AppWebAPIUtils.js   │      ├──  FooUtils.js   │      └──  __tests__   │              ├──  AppWebAPIUtils-­‐test.js   │              └──  FooUtils-­‐test.js   └──     ! AppDispatcher.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js Dispatcher Action Store View actions   └──  MessageActionCreators.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js
  27. 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
  28. 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
  29. REACT’S PARADIGM Based on Functional-Reactive principles Unidirectional data flow Composability

    Predictable, Reliable, Testable Declarative: what the UI should look like, given props
  30. 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
  31. 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
  32. 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
  33. ! ! var  React  =  require('react');   ! ! !

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

     render:  function()  {          //  TODO      }   ! });
  35. var  MessagesControllerView  =  React.createClass({   !    componentDidMount:  function()  {

             MessageStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          MessageStore.removeListener('change',  this._onChange);      },   !    render:  function()  {          //  TODO      }   ! }); //  MessagesControllerView.react.js  
  36. var  MessagesControllerView  =  React.createClass({   !    getInitialState:  function()  {

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

             return  {  messages:  MessageStore.getMessages()  };      },   !    componentDidMount:  function()  {          MessageStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          MessageStore.removeListener('change',  this._onChange);      },   !                   !    _onChange:  function()  {          this.setState({  messages:  MessageStore.getMessages()  });      }   ! });    render:  function()  {          //  TODO      }, //  MessagesControllerView.react.js  
  38. render:  function()  {      return  (      

       <ul>          </ul>      );   },
  39. render:  function()  {      var  messageListItems  =  [];  

       return  (          <ul>              {messageListItems}          </ul>      );   },
  40. render:  function()  {      var  messageListItems  =  this.state.messages.map(message  =>

     {          return  (              <MessageListItem                  key={message.id}                  messageID={message.id}                  text={message.text}              />          );      });      return  (          <ul>              {messageListItems}          </ul>      );   },
  41. //  MessageListItem.react.js   ! var  React  =  require('react');   !

    var  MessageListItem  =  React.createClass({   !    propTypes  =  {          messageID:  React.PropTypes.number.isRequired,          text:  React.PropTypes.string.isRequired      },   !    render:  function()  {          return  (              <li>                  {this.props.text}              </li>          );      }   ! });   ! module.exports  =  MessageListItem;
  42. //  MessageListItem.react.js   ! var  MessageActionCreators  =  require('MessageActionCreators');   var

     React  =  require('react');   ! var  MessageListItem  =  React.createClass({   !    propTypes  =  {          messageID:  React.PropTypes.number.isRequired,          text:  React.PropTypes.string.isRequired      },   !    render:  function()  {          return  (              <li  onClick={this._onClick}>                  {this.props.text}              </li>          );      },   !    _onClick:  function()  {          MessageActionCreators.deleteMessage(this.props.messageID);      }   ! });   ! module.exports  =  MessageListItem;
  43. //  MessageListItem.react.js   ! var  MessageActionCreators  =  require('MessageActionCreators');   var

     React  =  require('react');   ! var  MessageListItem  =  React.createClass({   !    propTypes  =  {          messageID:  React.PropTypes.number.isRequired,          text:  React.PropTypes.string.isRequired      },   !    render:  function()  {          return  (              <li  onClick={this._onClick}>                  {this.props.text}              </li>          );      },   !    _onClick:  function()  {          MessageActionCreators.deleteMessage(this.props.messageID);      }   ! });   ! module.exports  =  MessageListItem;
  44. Dispatcher Action Store View deleteMessage:  messageID  =>  {    

     AppDispatcher.dispatch({          type:  ActionTypes.MESSAGE_DELETE,          messageID      });   }
  45. Dispatcher Action Store View deleteMessage:  messageID  =>  {    

     AppDispatcher.dispatch({          type:  ActionTypes.MESSAGE_DELETE,          messageID      });   } case  ActionTypes.MESSAGE_DELETE:      delete  _messages[action.messageID];      this.emit('change');      break;  
  46. INITIALIZATION OF THE APP Usually done in a bootstrap module

    Initializes stores with an action Renders the topmost React component
  47. //  AppBootstrap.js   ! var  AppConstants  =  require('AppConstants');   var

     AppDispatcher  =  require('AppDispatcher');   var  AppRoot  =  require('AppRoot.react');   var  React  =  require('React');   ! require('FriendStore');   require('LoggingStore');   require('MessageStore');   ! module.exports  =  (initialData,  elem)  =>  {      AppDispatcher.dispatch({          type:  AppConstants.ActionTypes.INITIALIZE,          initialData      });      React.render(<AppRoot  />,  elem);   };
  48. 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.
  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
  50. MORE FLUX PATTERNS LoggingStore Error handling with client ID /

    dirty bit Error handling with actions queue Resolving dependencies in the Controller-view
  51. ANTI-PATTERNS FOR REACT+FLUX Getters in render() Public setters in stores

    & the setter mindset trap Application state/logic in React components Props in getInitialState()