📜 ⬆️ ⬇️

Flux for stupid people

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. A little earlier, I shared with Habravchana 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 missed this article when I tried to deal with Flux. It was not easy: there is no good documentation and many of its parts are being moved.

This is a continuation of the article "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 set of static views and you do not save or update data, then no. Flux will not give you any benefits.

Why Flux?


The humor is that Flux is not the easiest idea. So why complicate things?

90% of iOS apps are tabular data. IOS tools have well-defined views and a data model that simplify 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 worked in this field for many years and “best practices” have never taught us this. Instead, we were “taught” by the library. jQuery? Angular? Backbone? The real problem - the flow of data - still eludes us.

What is flux?


Flux is a term coined to mean a unidirectional data stream with very specific events and listeners. There are no Flux libraries (comment perev .: at the moment they are full ) , but you will need the Flux Dispatcher and any JavaScript event library .

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

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

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

1. Your submissions send events


Dispatcher is essentially an event system. It translates events and logs callbacks. There is only one global dispatcher. You can use dispatcher from facebook . It is very easy to initialize:

var AppDispatcher = new Dispatcher(); 

Let's say your application has a “New Item” button that adds a new item to the list.

 <button onClick={ this.createNewItem }>New Item</button> 

What happens when you click? Your submission 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 (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 .

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

 // 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 the event sent:

 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 the traditional approach to how Flux calls callbacks. The payload object contains the event name and data. And the switch statement decides what action to perform.

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 images and other metadata, you create another storage and name 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 storage is enough for you.

Only repositories register callbacks in a dispatcher. Your submissions should never call AppDispatcher.register. Dispatcher only to send messages from views to repositories. Your presentations will respond to a different kind of event.

3. Your repository sends a "Change" event.


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

Your repository sends an event, but does not use dispatcher. This can be confusing, but it is the “Flux way”. Let's give our repository the ability to trigger an event. If you use MicroEvents.js , then it 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 transfer data with the event. Our view is to worry only about something that has changed.

4. Your presentation responds to the event "change"


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

First, let's subscribe to the "change" event from our ListStore immediately 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 delete the listener when the component is deleted:

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

Now what? Let's take a look at our render function, which I intend to leave at 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 <li key={ listItem.id }> { listItem.name } </li>; }); return <div> <ul> { itemHtml } </ul> <button onClick={ this.createNewItem }>New Item</button> </div>; } 

We have come to a full cycle. When you add a new item, the view sends an event, the repository subscribes to this event, the repository changes, the repository creates a 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 it 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 the call to render is different from the previous one. Your render function actually generates a “virtual DOM”, which 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 a repository changes, your views should not care what event occurred: add, delete, or change. They should just completely redraw. The “virtual DOM” comparison algorithm will cope with heavy calculations and change the real DOM. It will make your life easier and reduce the headache.

And also: what is “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 a lot of your views use this event. Plus, all views must 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 submission simply calls ListAction.add ({name: “...”}) and is not concerned about the syntax of sending messages.

Last questions


All Flux tells us is how to control the flow of data. But he does not answer the questions:


The answer to all these questions: have fun!

PS: Do not use forceUpdate
I used forseUpdate for the sake of simplicity. The correct solution will read the data from the repository and copy it into the state of the component, and in the render function read the data from the 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 it runs synchronously, and setState is more efficient.

That's all!

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

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

The Flux documentation contains several useful examples deeply buried inside.

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

Source: https://habr.com/ru/post/249279/


All Articles