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

react-flux-fluent-2015-notes

 react-flux-fluent-2015-notes

Bill Fisher

April 21, 2015
Tweet

More Decks by Bill Fisher

Other Decks in Technology

Transcript

  1. React + Flux Two Great Tastes that Taste Great Together

    Bill Fisher @fisherwebdev #ReactJS
  2. React https://speakerdeck.com/fisherwebdev/react-flux-fluent-2015 Flux + React is a framework for building

    UI, for the view layer. Flux is a pattern, not a framework, for managing application state. ! There’s a lot of excitement about React these days. Lots of people are using React and Flux - Khan Academy, Yahoo, Airbnb, Soundcloud, and many others. ! React and Flux were created separately, but they compliment each other as each is based on similar ideas: a unidirectional data flow continuous change declarative code over imperative code simplicity !
  3. Model Model Model Model Eventually, we need a feature that

    requires derived state. We need to know the state of one model within another model, so that model will know if or how it should update itself.
  4. Model Model Model Model The model with derived state is

    now a second class citizen in the app.
  5. Model Model Model Model Additional features will create new dependency

    trees. Now we have a weave of dependencies that we need to maintain, reason about, and be able to explain to new members of our team.
  6. And eventually, we might have no single source of truth

    in the application as our application state has become a tangled weave of data dependencies. It’s very difficult to reason about an application like this. As changes cascade through the data layer, our application state becomes highly unstable.
  7. "Tangled wires, Freegeek, Portland, Oregon, USA" by Cory Doctorow, used

    under CC BY 2.0\ What we have is complete mess. !
  8. "Spaghetti? Yum!" by Dan McKay, used under CC BY 2.0

    It’s a form of spaghetti code. ! Many people deal with this with an evented, publish-subscribe approach, but this just turns our spaghetti code into invisible spaghetti code, making it even more unstable as we have no control over the order in which the events will propagate changes through the application. ! So at Facebook, we took a look at this problem, and instead of going down a traditional MVC path, we created Flux.
  9. Dispatcher Action Store View This is Flux: - avoid spaghetti

    code - grow apps with confidence - bring on new people quickly - mental models matter! Four major parts: action, dispatcher, store, views (React)
  10. 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
  11. 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
  12. Actions & Action Creators Actions: an object with a type

    property and new data, like events Action creators: semantic methods that create actions collected together in a module to become an API Actions are a lot like events, in that they have a type and they carry some data along with them. ! Actions creators are just methods that hand actions to the dispatcher. ! We collect them together to make them easy to find at a later time, and this library becomes the API.
  13. "A copy of the San Francisco Chronicle from when the

    Apollo 11 mission made it to the moon." by zpeckler, used under CC BY 2.0 Actions should be like newspapers, reporting some something that has happened in the world. For example, the user clicked, the server responded, or the browser completed an animation frame.
  14. //  MessageActionCreators.js   ! var  AppDispatcher  =  require('../AppDispatcher');   var

     AppConstants  =  require('../AppConstants');   ! var  ActionTypes  =  Constants.ActionTypes;   ! module.exports  =  {   !     ! ! ! ! ! ! }; messageCreated:  text  =>  {      AppDispatcher.dispatch({          type:  ActionTypes.MESSAGE_CREATED,          text      });   }, Here’s what they look like inside the action creators module. Let’s focus on a single action creator.
  15. messageCreated:  text  =>  {      AppDispatcher.dispatch({      

       type:  ActionTypes.MESSAGE_CREATED,          text      });   },
  16.    messageCreated:  text  =>  {          AppDispatcher.dispatch({

                 type:  ActionTypes.MESSAGE_CREATED,              text          });      } Here we can see the action itself in yellow. The action creator is simply handing that yellow action object to the dispatcher. ! Note that I’m using the past tense to name both the method and the type. ! I could have named this “createMessage” but instead I’ve named it “messageCreated”. ! I’m doing that to keep myself focused on the idea that this is a report on something that has already happened, not an imperative command involving implementation details. !
  17. 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
  18. 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
  19. The Dispatcher "Sunset at Sutro" by ohad*, used under CC

    BY 2.0 The dispatcher is like a radio tower, indiscriminately broadcasting the action to all of the stores.
  20. Dispatcher Action Store View It’s also like the the center

    of a dipole. All data flows through the dispatcher. ! Diagrams like this, showing magnetic flux, were the mental image in Jing Chen’s mind when she named Flux.
  21. Available through npm or Bower as flux A singleton registry

    of callbacks, similar to EventEmitter dispatch(action): invokes all callbacks, provides action Primary API: dispatch(), register(), waitFor() The Dispatcher
  22. waitFor() is a distinctive feature, vital for derived state Cannot

    dispatch within a dispatch Each dispatch is a discrete moment of change The Dispatcher
  23. //  AppDispatcher.js   ! var  Dispatcher  =  require('Flux.Dispatcher');   !

    //  export  singleton   module.exports  =  new  Dispatcher();
  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   └──     ! AppDispatcher.js stores   ├──  MessageStore.js   └──  __tests__          └──  MessageStore-­‐test.js Dispatcher Action Store View actions   └──  MessageActionCreators.js views   ├──  MessageControllerView.react.js   └──  MessageListItem.react.js
  26. Stores Courtesey of The Bancroft Library, UC Berkeley BANC PIC

    1905.17500 v.10:141--ALB Stores are highly decoupled domain models and are the locus of control in the application.
  27. 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 That is, they manage the state for a logical domain: we might have a FriendStore, a PhotoStore or a MessageStore where a collection is managed, or we might have something more abstract like a TimeStore. ! Collections or values may be managed - any way to manage state that makes sense.
  28. 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 How they work: 1. Register a callback with dispatcher - this is the only way into the store. 2. When state changes, they emit change to alert the view layer. ! If data dependencies occur, we use waitFor()
  29. //  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_CREATED:                      var  message  =  {                          id:  Date.now(),                          text:  action.text                      }                      _messages[message.id]  =  message;                      this.emit('change');                      break;   !                case  ActionTypes.MESSAGE_DELETED:                      delete  _messages[action.messageID];                      this.emit('change');                      break;   !                default:                      //  no  op              }   !        });   private variables at the top getters at the bottom the real action is in the registered callback
  30.        _dispatchToken  =  AppDispatcher.register(action  =>  {   !

               switch(action.type)  {   !                case  ActionTypes.MESSAGE_CREATED:                      var  message  =  {                          id:  Date.now(),                          text:  action.text                      }                      _messages[message.id]  =  message;                      this.emit('change');                      break;   !                case  ActionTypes.MESSAGE_DELETED:                      delete  _messages[action.messageID];                      this.emit('change');                      break;   !                default:                      //  no  op              }   !        });   respond to action types, or do nothing if this store is not interested emit change after modifying private variables that hold application state
  31. 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/ http://facebook.github.io/flux/docs/testing-flux- applications.html
  32. callback  =  AppDispatcher.register.mock.calls[0][0];   Give me a reference to the

    first argument of the first call to AppDispatcher's register() method.
  33. 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_CREATED,              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];  
  34. 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
  35. 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
  36. 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 in a Flux application are a tree of React components. Near the top of that tree, we have some special views that we call “controller” views - that’s a somewhat controversial name - they aren’t really controllers. Some people use the name containers, but I think a better name might be “querying views”. The thing that differentiates them is that they query the stores that have changed and provide that data to their children, and the data flows down the tree.
  37. React & the DOM Component-based framework for managing DOM updates

    Uses a “Virtual DOM”: data structure and diff algorithm Updates the DOM as efficiently as possible Huge performance boost Bonus: we can stop thinking about managing the DOM Some people describe React as a component based framework for managing DOM updates.
  38. React’s Paradigm Based on Functional-Reactive principles Unidirectional data flow Composability

    Predictable, Reliable, Testable Declarative: what the UI should look like, given props React is more than that, however. The real innovation with React is its programming paradigm. The Virtual DOM is merely an implementation detail that helps to provide us with that paradigm. The really great thing about React is that it’s based on functional-reactive principles. ! We can think of each function in React as being like a pure function in functional programming. And just like pure functions, we can compose them together, with the data output of each function leading into the next, in a unidirectional data flow. ! And like pure functions, React components are very predictable, reliable and testable. ! We can declare what our UI should look like, given any props.
  39. Using React Data is provided through props Rendering is a

    function of this.props and this.state Keep components as stateless as possible “Re-render” (or not) on every state change or new props Component lifecycle and update cycle methods Props are like the arguments of the component. They are how we provide data to the component. ! And in fact rendering is a function of this.props and this.state. Props come from the outside, but state in an internal map of values. ! But if we’re smart about it, we’ll avoid using this.state and we’ll keep our components as stateless as possible. This keeps our components pure, like pure functions: that is, we will always get the same result, given specific values for props. ! We can also freely re-render at will — this complements Flux very nicely, as we can immediately see any changes in our application state reflected in our UI. ! React also provides us with hooks into each component’s lifecycle and update cycles.
  40. 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   Here is a component where I’m going to use four lifecycle methods to make it a controller view.
  41. 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   Here they are in yellow. I’m just using these methods to listen to the store, and to set up some initial state by pulling data from the store. ! I’ll also create an onChange handler so that I can update that state. Whenever I call setState to update a React component’s state it will re-render itself and all of its children.
  42. render:  function()  {      var  messageListItems  =  this.state.messages.map(message  =>

     {          return  (              <MessageListItem                  key={message.id}                  messageID={message.id}                  text={message.text}              />          );      });      return  (          <ul>              {messageListItems}          </ul>      );   }, Now in the render method, we can map over the messages we’ve stored in this.state and create a child component — a list item — out of each one. ! — ! provide unique key for arrays of children
  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.messageDeleted(this.props.messageID);      }   ! });   ! module.exports  =  MessageListItem; Here’s our list item. It’s going to raise an error if we haven’t passed in the correct data as props. We could lift the hard requirement and show a spinner instead. onClick handler will be automatically handled by event delegation In that handler, we’ll call an action creator to express what the user has done.
  44. Dispatcher Action Store View messageDeleted:  messageID  =>  {    

     AppDispatcher.dispatch({          type:  ActionTypes.MESSAGE_DELETED,          messageID      });   } case  ActionTypes.MESSAGE_DELETED:      delete  _messages[action.messageID];      this.emit('change');      break;   _onChange:  function()  {      this.setState({            messages:  MessageStore.getMessages()        });   } Now we’ve come full circle. ! First our action will get handed to the dispatcher by the action creator. Then our store will handle that action and emit change. Then our controller view will respond to the change event by querying for new data, and update itself and all of its children.
  45. Initialization of the App Usually done in a bootstrap module

    Initializes stores with an action Renders the topmost React component
  46. //  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);   };
  47. 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.
  48. More Flux Patterns LoggingStore Error handling with client ID /

    dirty bit Error handling with actions queue Resolving dependencies in the Controller-view
  49. Anti-patterns Application state/logic in React components Getters in render() Public

    setters in stores & the setter mindset trap Props in getInitialState()