📜 ⬆️ ⬇️

Understanding Flux, reactive architecture from facebook



Introduction


Welcome to Part Three of the Study React series. Today, we will explore how Facebook Flux architecture works and how to use it in our projects.

First of all, I advise you to read the first two articles in this series, Getting Started & Concepts and Building a Real Time Twitter Stream with Node and React . Reading them is not mandatory, but it can certainly help you understand this article if you are not familiar with React.js.

What is flux?


Flux is the architecture that the Facebook team uses when working with React. This is not a framework or library, it is a new architectural approach that complements React and the principle of a unidirectional data flow.
')
However, Facebook provides a repository that contains the Dispatcher implementation. The dispatcher plays the role of a global intermediary in the Publisher Subscriber template (Pub / sub) and distributes the payload to registered handlers.

A typical implementation of the Flux architecture can use this library along with the EventEmitter class from NodeJS to build an event-oriented system that will help manage the state of the application.

Flux is probably the easiest to explain, based on its constituent components:


Let's see how this process looks like a diagram:


How does the API relate to this?

In my opinion, using Actions to transfer data to the Stores through the Flux stream is the least painful way of working with data coming from outside your program or going outside.

Dispatcher / Dispatcher


What is a dispatcher?

In essence, the Dispatcher is the manager of the whole process. This is the central hub of your application. The dispatcher receives the action as input and sends these actions (and associated data) to registered handlers.

So is this really pub / sub?

Not really. The dispatcher sends data to ALL processors registered in it and allows you to call handlers in a specific order, even wait for updates before continuing. There is only one Dispatcher, and it acts as the central hub of your entire application.

Here is what it might look like:

var Dispatcher = require('flux').Dispatcher; var AppDispatcher = new Dispatcher(); AppDispatcher.handleViewAction = function(action) { this.dispatch({ source: 'VIEW_ACTION', action: action }); } module.exports = AppDispatcher; 

In the example above, we create an instance of the Manager and the handleViewAction method. This abstraction is useful if you are going to separate the actions created in the interface and the actions that came from the server / API.

Our method calls the dispatch method, which already sends action data to all handlers registered in it. This action can then be processed by the Storages, as a result of which the state of the application will be updated.

The following diagram illustrates this process:


Dependencies


One of the nice details of the described implementation of the Controller is the ability to describe dependencies and control the order of execution of handlers in the Storages. So, if for correct state display one of the application components depends on another one, which must be updated before it, the waitFor Dispatcher method is useful.

To use this feature, you need to store the value returned from the registration method in the Manager in the store's DispatcherIndex property, as shown below:

 ShoeStore.dispatcherIndex = AppDispatcher.register(function(payload) { }); 

Then, in the Storage, when processing the Action, we can use the waitFor method of the Manager to make sure that by this moment ShoeStore has already managed to process the Action and update the data:

 case 'BUY_SHOES': AppDispatcher.waitFor([ ShoeStore.dispatcherIndex ], function() { CheckoutStore.purchaseShoes(ShoeStore.getSelectedShoes()); }); break; 

Note Lane: Ken Wheeler, obviously, describes an outdated implementation of the Manager, since the waitFor method has a different signature in the current version.

Stores / Storages


Flux repositories control the state of certain parts of the application domain. At a higher level, this means that the Stores store data, methods for obtaining this data, and Action handlers registered in the Manager.

Let's take a look at simple storage:

 var AppDispatcher = require('../dispatcher/AppDispatcher'); var ShoeConstants = require('../constants/ShoeConstants'); var EventEmitter = require('events').EventEmitter; var merge = require('react/lib/merge'); //     shoes var _shoes = {}; //    shoes    function loadShoes(data) { _shoes = data.shoes; } //   Event Emitter  Node var ShoeStore = merge(EventEmitter.prototype, { //   shoes getShoes: function() { return _shoes; }, emitChange: function() { this.emit('change'); }, addChangeListener: function(callback) { this.on('change', callback); }, removeChangeListener: function(callback) { this.removeListener('change', callback); } }); //     AppDispatcher.register(function(payload) { var action = payload.action; var text; //        switch(action.actionType) { case ShoeConstants.LOAD_SHOES: //        loadShoes(action.data); break; default: return true; } //    ,   "change" ShoeStore.emitChange(); return true; }); module.exports = ShoeStore; 

The most important thing we did in the example above was that we added EventEmitter from NodeJS to our repository. This allows repositories to listen and send events, which, in turn, allows the components of the view to be updated based on these events. Since our view listens to the “change” event created by the Vaults, it learns that the state of the application has changed, and it's time to get (and display) the current state.

We also registered the handler in our AppDispatcher using its register method. This means that now our Vault now listens to alerts from AppDispatcher. Based on the data received, the switch statement decides whether we can process the Action. If the action has been processed, a “change” event is created, and Views that subscribe to this event react to it by updating their state:


The view uses the Store interface's getShoes method to get all the shoes from the _shoes internal object and pass this data to the components. This is a very simple example; however, this architecture allows components to remain fairly accurate, even if instead of Views you use more complex logic.

Action Creators & Actions / Factory Actions and Actions


An Action Factory is a set of methods that are called from Views (or from any other place) to send Actions to the Dispatcher. Actions are the payload that the Dispatcher sends to subscribers.

In the Facebook implementation, Actions differ in type — a constant that is sent along with the action data. Depending on the type, Actions can be appropriately processed in registered handlers, and the data from these Actions are used as arguments to internal methods.

Here are the constant declarations:

 var keyMirror = require('react/lib/keyMirror'); module.exports = keyMirror({ LOAD_SHOES: null }); 

Above, we used the keyMirror library from React to, as you guessed, create an object with values ​​identical to its keys. Just by looking at this file, you can say that our application can download shoes. Using constants allows you to streamline everything and helps you quickly assess the capabilities of the application.

Let's now look at the announcement of the relevant Action Factory:

 var AppDispatcher = require('../dispatcher/AppDispatcher'); var ShoeStoreConstants = require('../constants/ShoeStoreConstants'); var ShoeStoreActions = { loadShoes: function(data) { AppDispatcher.handleAction({ actionType: ShoeStoreConstants.LOAD_SHOES, data: data }) } }; module.exports = ShoeStoreActions; 

In the example above, we created a method in our object, ShoeStoreActions, that passes this data to our Manager. Now we can load this file from our API (or, for example, Views) and call the ShoeStoreActions.loadShoes (ourData) method to transfer the payload to the Dispatcher, which will send it to subscribers. Thus, ShoeStore will know about this event and will call the boot method of some shoes.

Controller Views / Views


Views are just React components that subscribe to the “change” event and get the state of the application from the Repositories. Then they pass this data to the child components through properties.


Here is what it looks like:

 /** @jsx React.DOM */ var React = require('react'); var ShoesStore = require('../stores/ShoeStore'); //        function getAppState() { return { shoes: ShoeStore.getShoes() }; } //  React- var ShoeStoreApp = React.createClass({ //   getAppState,     getInitialState: function() { return getAppState(); }, //    componentDidMount: function() { ShoeStore.addChangeListener(this._onChange); }, //    componentWillUnmount: function() { ShoesStore.removeChangeListener(this._onChange); }, render: function() { return ( <ShoeStore shoes={this.state.shoes} /> ); }, //        "change" _onChange: function() { this.setState(getAppState()); } }); module.exports = ShoeStoreApp; 

Note Trans .: In the current version of React, components are created slightly differently .

In the example above, we subscribe to the Storage updates using addChangeListener and update our state when we receive the "change" event.

The state of the application is stored in our Storages, so we use the Storages interface to get this data and then update the state of the components.

Putting it all together


Now that we have walked through all the main parts of the Flux architecture, we better understand how this architecture really works. Remember our process diagram from the beginning of the article? Let's take a closer look at them, as we now understand the functions of each part of the stream:



Conclusion


Hope this article has helped you better understand the Flux architecture from Facebook. I didn’t even know how comfortable React.js was until I tried it in action.

Having once used Flux, you will feel that writing applications on React without Flux is similar to manipulating DOM without jQuery. Yes, it is possible, but it looks less elegant and orderly.

If you want to stick with the Flux architecture, but you don’t like React, try Delorean , a Flux framework that can be combined with Ractive.js or Flight. Another noteworthy library is Fluxxor , which uses a slightly different approach to the Flux architecture and implies a more rigid connection of the Flux components in a single instance.

I believe that in order to fully understand Flux, it must be tested in action, so stay with us to read the fourth , final part of a series of articles on learning React, where we will create a simple online store using React.js and Flux architecture .

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


All Articles