Back to Home

Flux for stupid people

javascript · reactjs · flux

Flux for stupid people

Original author: Andrew Ray
  • Transfer
  • Tutorial
Trying to deal with the library from Facebook ReactJS and the Flux architecture promoted by the same company, I came across two interesting articles on the Internet: ReactJS For Stupid People and Flux For Stupid People. Earlier, I shared with the Habrachians the translation of the first article, it was the turn of the second. So let's go.

Flux for stupid people


TL; DR I, as a stupid person, just lacked this article when I tried to deal with Flux. It was not easy: there is no good documentation and many of its parts are moved.

This is a follow-up to ReactJS For Stupid People .

Should I use Flux?


If your application works with dynamic data, then you should probably use Flux.

If your application is just a collection of static views and you are not saving or updating data, then no. Flux will not give you any benefits.

Why flux?


The humor is that Flux is not an easy idea. So why complicate things?

90% of iOS apps are tabular data. IOS tools have well-defined views and a data model that simplifies application development.

For frontend'a (HTML, JavaScript, CSS) we do not have this. Instead, we have a big problem: no one knows how to structure the frontend application. I have been working in this area for many years and the “best practices” have never taught us this. Instead, we were “taught” by libraries. jQuery? Angular? Backbone? The real problem - data flow - is still eluding us.

What is Flux?


Flux is a term coined to refer to a unidirectional data stream with very specific events and listeners. There are no Flux libraries (approx. Transl .: currently full of them ) , but you will need Flux Dispatcher and any JavaScript event-library .

The official documentation is written as someone's stream of consciousness and is a poor starting point. But when you put Flux in your head, it can help fill in some gaps.

Do not try to compare Flux with MVC architecture. Drawing parallels will only confuse you even more.

Let's dive deeper! I will explain all the concepts in order and reduce them to one.

1. Your submissions post events


Dispatcher is essentially an event system. It broadcasts events and registers callbacks. There is only one global dispatcher. You can use the dispatcher from Facebook . It is very easy to initialize:

var AppDispatcher = new Dispatcher();

Say your application has a “New Item” button that adds a new item to the list.


What happens when I click? Your view sends a special event that contains the name of the event and the data of the new element:

createNewItem: function( evt ) {
    AppDispatcher.dispatch({
        eventName: 'new-item',
        newItem: { name: 'Marco' } // example data
    });
}


2. Your store responds to events sent


Like Flux, Store is just a term coined by Facebook. For our application, we need some set of logic and data for the list. This is our repository. Let's call it ListStore .

Repository is a singleton, which means that you can not declare it through the new operator .

// Global object representing list data and logic
var ListStore = {
   // Actual collection of model data
    items: [],
   // Accessor method we'll use later
    getAll: function() {
        return this.items;
    }
}

Your repository will respond to a sent event:

var ListStore = …
AppDispatcher.register( function( payload ) {
    switch( payload.eventName ) {
        case 'new-item':
           // We get to mutate data!
            ListStore.items.push( payload.newItem );
            break;
    }
    return true; // Needed for Flux promise resolution
});

This is a traditional approach to how Flux calls callbacks. The payload object contains the name of the event and data. And the switch statement decides which action to take.

Key Concept : Storage is not a model. The repository contains models .

Key concept : Storage is the only entity in your application that knows how to change data. This is the most important part of Flux. The event we sent does not know how to add or remove an item
.

If, for example, different parts of your application need to store the path to some pictures and other metadata, you create another repository and call it ImageStore. Storage is a separate “domain” of your application. If your application is large, the domains may be obvious to you. If the application is small, then perhaps one repository is enough.

Only vaults register callbacks in the dispatcher. Your views should never call AppDispatcher.register. Dispatcher is only for sending messages from views to repositories. Your ideas will respond to a different kind of event.

3. Your store sends a “Change” event


We are almost done. Now our data are definitely changing, it remains to tell the world about it.

Your repository dispatches an event but does not use a dispatcher. This may be confusing, but this is the “Flux way”. Let's give our store the ability to trigger an event. If you use MicroEvents.js , then this is simple:

MicroEvent.mixin( ListStore ); 

Now we initialize our event “change”:

    case 'new-item':
            ListStore.items.push( payload.newItem );
           // Tell the world we changed!
            ListStore.trigger( 'change' );
            break;

Key concept: We do not transmit data with the event. Our idea is to worry only that something has changed.

4. Your performance responds to the “change” event


Now we have to display the list. Our view is completely redrawn . when the list changes. This is not a typo.

First, let's subscribe to the “change” event from our ListStore right after creating the component:

 componentDidMount: function() {  
    ListStore.bind( 'change', this.listChanged );
}

For simplicity, we simply call forceUpdate, which will cause a redraw:

listChanged: function() {  
   // Since the list changed, trigger a new render.
    this.forceUpdate();
},

Do not forget to remove the listener when the component is removed:

componentWillUnmount: function() {  
    ListStore.unbind( 'change', this.listChanged );
},

Now what? Let's look at our render function, which I intentionally left for last:

render: function() {
   // Remember, ListStore is global!
   // There's no need to pass it around
    var items = ListStore.getAll();
   // Build list items markup by looping
   // over the entire list
    var itemHtml = items.map( function( listItem ) {
       // "key" is important, should be a unique
       // identifier for each list item
        return 
  • { listItem.name }
  • ; }); return
      { itemHtml }
    ; }

    We have come to the full cycle. When you add a new item, the view dispatches the event, the store is subscribed to this event, the store changes, the store creates the “change” event and the view subscribed to the “change” event is redrawn.

    But there is one problem. We completely redraw the view every time the list changes! Isn't that terribly inefficient?

    Of course, we call the render function again and, of course, all the code in this function is executed. But R eact changes the real DOM, if only the result of calling render is different from the previous one.Your render function actually generates a “virtual DOM” that React compares with the previous result of calling the render function. If the two virtual DOMs are different, React will change the real DOM - and only in the right places.

    Key concept: When the repository changes, your views should not care about what event happened: add, delete or change. They should just be completely redrawn. The “virtual DOM” comparison algorithm will cope with difficult calculations and change the real DOM. It will make your life easier and reduce your headache.

    And one more thing: what is an “Action Creator" in general?
    Remember, when we pressed our button, we sent a special event:

    AppDispatcher.dispatch({  
        eventName: 'new-item',
        newItem: { name: 'Samantha' }
    });
    

    This can lead to frequently repeated code if many of your views use this event. Plus, all submissions should be aware of the format. It is not right. Flux offers an abstraction called action creators , which simply abstracts the code above into a function.

    ListActions = {
        add: function( item ) {
            AppDispatcher.dispatch({
                eventName: 'new-item',
                newItem: item
            });
        }
    };
    

    Now, your view simply calls ListAction.add ({name: “...”}) and does not worry about the syntax for sending messages.

    Last questions


    All Flux tells us is how to control the flow of data. But he does not answer questions:
    • How do you upload data to the server and how to save it on the server?
    • How to manage communication between components with a common parent?
    • What event library to use? Does it matter?
    • Why hasn't Facebook included all this in its library?
    • Should I use a model layer like Backbone as a model in our repository?


    The answer to all these questions: have fun!

    PS: Do not use forceUpdate
    I used forseUpdate for the sake of simplicity. The correct solution is to read the data from the storage and copy it to the state of the component, and in the render function read the data from state. You can see how this works in this example .

    When your component loads, the repository copies the data to state. When the storage changes, the data is completely overwritten . And this is better, because inside forceUpdate is executed synchronously, and setState is more efficient.

    That's all!

    In addition, you can watch Example Flux Applcation from Facebook .

    I hope that after reading this article it will be easier for you to understand the file structure of the project.

    The Flux documentation contains some useful examples deeply buried inside.

    If this post helped you understand Flux, then follow me on Twitter .

    Read Next