flux-applicative

83428d816d84e071104956e9c7726305?s=47 Bill Fisher
February 27, 2015

 flux-applicative

83428d816d84e071104956e9c7726305?s=128

Bill Fisher

February 27, 2015
Tweet

Transcript

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

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

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

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

  5. https://speakerdeck.com/fisherwebdev/flux-applicative React and Flux

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

  7. https://speakerdeck.com/fisherwebdev/flux-applicative

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

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

    Modified from original https://speakerdeck.com/fisherwebdev/flux-applicative
  10. Model Model Model Model

  11. Model Model Model Model

  12. Model Model Model Model

  13. Model Model Model Model

  14. Model Model Model Model

  15. None
  16. None
  17. None
  18. "Tangled wires, Freegeek, Portland, Oregon, USA" by Cory Doctorow, used

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

  20. Dispatcher Action Store View

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

    Modified from original
  22. 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
  23. 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
  24. 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
  25. 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
  26. actions   └──  MessageActionCreators.js Action ACTIONS & ACTION CREATORS

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

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

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

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

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

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

         └──  MessageStore-­‐test.js STORES
  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
  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
  42. //  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              }   !        });  
  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              }   !        });  
  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 http://facebook.github.io/jest/
  45. callback  =  AppDispatcher.register.mock.calls[0][0];  

  46. 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];  
  47. 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
  48. 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
  49. View VIEWS & CONTROLLER VIEWS views   ├──  MessageControllerView.react.js  

    └──  MessageListItem.react.js
  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
  51. REACT

  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
  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
  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
  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
  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
  57. ! ! var  React  =  require('react');   ! ! !

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

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

             MessageStore.on('change',  this._onChange);      },   !    componentWillUnmount:  function()  {          MessageStore.removeListener('change',  this._onChange);      },   !    render:  function()  {          //  TODO      }   ! }); //  MessagesControllerView.react.js  
  60. 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  
  61. 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  
  62.    render:  function()  {          //  TODO

         },
  63. render:  function()  {      return  (      

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

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

     {          return  (              <MessageListItem                  key={message.id}                  messageID={message.id}                  text={message.text}              />          );      });      return  (          <ul>              {messageListItems}          </ul>      );   },
  66. //  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;
  67. //  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;
  68. //  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;
  69. Dispatcher Action Store View

  70. Dispatcher Action Store View deleteMessage:  messageID  =>  {    

     AppDispatcher.dispatch({          type:  ActionTypes.MESSAGE_DELETE,          messageID      });   }
  71. 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;  
  72. INITIALIZATION OF THE APP Usually done in a bootstrap module

    Initializes stores with an action Renders the topmost React component
  73. //  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);   };
  74. 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.
  75. IMMUTABLE DATA Boost performance in React’s shouldComponentUpdate() React.addons.PureRenderMixin immutable-js: http://facebook.github.io/immutable-js/

  76. 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
  77. MORE FLUX PATTERNS LoggingStore Error handling with client ID /

    dirty bit Error handling with actions queue Resolving dependencies in the Controller-view
  78. 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()
  79. FLUX Dataflow Programming CQRS Functional and FRP MVC Reactive

  80. FLUX TOTALLY WORKS Dispatcher Action Store View

  81. THANK YOU!