πŸ“œ ⬆️ ⬇️

RefluxJS - an alternative look at Flux architecture from Facebook

From the translator: looking at ReactJS and inspired by its simplicity, I began to look for a library that would provide the same simple data exchange within my application. Stumbled upon Flux , saw code samples and went to look for an alternative. I came across RefluxJS, immediately fell in love and went to translate the official dock. It was written just in the style of the article, so first of all I decided to share it with the Chabrasoobschestvom. The translation is somewhat free. In some places, if it seemed to me that something needs additional clarification or an example, I was not shy.

In the translation below, the term β€œevent” is sometimes used as a translation for the term Action from Reflux, and sometimes the term β€œaction” is used, depending on the context. I could not find a better translation. If you have options, look forward to suggestions in the comments.

Overview


image image image image image
')
RefluxJS is a simple library that provides a unidirectional data stream in your application that uses the Flux concept from Facebook.

You can read a review of the Flux architecture here , and in this article an alternative option will be described, to a greater extent using the functional programming capabilities, with a departure from the MVC architecture towards a unidirectional data flow.

╔═════════╗ ╔════════╗ ╔═════════════════╗ β•‘ Actions ║──────>β•‘ Stores ║──────>β•‘ View Components β•‘ β•šβ•β•β•β•β•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• ^ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ 

The pattern consists of actions (actions) and data warehouses (stores). The actions initiate the movement of data through events through the repositories to the visual components. If the user has done something, an appropriate event is generated using the action. A data warehouse has been signed for this event. It handles the event and, possibly, in turn generates some of its own.

For example, the user has changed the list filtering in the application. The filter component generates the "filter changed" event. The repository responds to this by executing an ajax request with an updated filter and, in turn, informs everyone subscribing to it that the data set supplied by it has changed.

Content



Comparing Reflux and Facebook Flux


The goal of the RefluxJS project is to more easily and quickly integrate the Flux architecture into your project both on the client side and on the server side. However, there are some differences between how RefluxJS works and what the classic Flux architecture offers. More information is in this blog post .

Similarities with Flux

Some RefluxJS concepts are similar to Flux:

Differences from Flux

RefluxJS is an improved version of the Flux-concept, more dynamic and friendlier to functional reactive programming:

Examples


Some examples can be found at the following addresses:

Installation


Currently RefluxJS can be installed using npm or using bower.

NPM

To install using npm, run the following command:

  npm install reflux 


Bower

To install using bower:

  bower install reflux 

ES5

Like React, RefluxJS requires es5-shim for legacy browsers. You can take it here.

Using


A full example can be found here .

We create actions

Actions are created by calling `Reflux.createAction ()`. As a parameter, you can pass a list of options.

 var statusUpdate = Reflux.createAction(options); 

The action object is a functor , so you can call it by referring to the object as a function:

 statusUpdate(data); //   statusUpdate,     data statusUpdate.triggerAsync(data); //  ,   

If `options.sync` is set to true, the event will be triggered as a synchronous operation. This setting can be changed at any time. All following calls will use the set value.

For convenient creation of a large number of actions, you can do this:

 var Actions = Reflux.createActions([ "statusUpdate", "statusEdited", "statusAdded" ]); //   Actions    ,      createActions(). //      Actions.statusUpdate(); 

Asynchronous work with actions

For events that can be handled asynchronously (for example, API calls) there are several different work options. In the most general case, we consider the successful completion of processing and error. To create different events in this version, you can use `options.children`.

 //   'load', 'load.completed'  'load.failed' var Actions = Reflux.createActions({ "load": {children: ["completed","failed"]} }); //      'load',   , //      ,   failed  completed Actions.load.listen( function() { //      . //      this someAsyncOperation() .then( this.completed ) .catch( this.failed ); }); 


For the considered case there is a special option: `options.asyncResult`. The following action definitions are equivalent:

 createAction({ children: ["progressed","completed","failed"] }); createAction({ asyncResult: true, children: ["progressed"] }); 


The following methods are available for automatically calling child actions `completed` and` failed`:

The following three definitions are equivalent:

 asyncResultAction.listen( function(arguments) { someAsyncOperation(arguments) .then(asyncResultAction.completed) .catch(asyncResultAction.failed); }); asyncResultAction.listen( function(arguments) { asyncResultAction.promise( someAsyncOperation(arguments) ); }); asyncResultAction.listenAndPromise( someAsyncOperation ); 

Asynchronous actions as promises

Asynchronous actions can be used as promises. This is especially convenient for rendering on the server when you need to wait for the successful (or not) completion of some event before rendering.

Suppose we have an action and a repository and we need to execute an API request:

 //     `completed` & `failed` "" var makeRequest = Reflux.createAction({ asyncResult: true }); var RequestStore = Reflux.createStore({ init: function() { this.listenTo(makeRequest, 'onMakeRequest'); }, onMakeRequest: function(url) { // ,  `request` - - HTTP  request(url, function(response) { if (response.ok) { makeRequest.completed(response.body); } else { makeRequest.failed(response.error); } }) } }); 


In this case, you can use promises on the server to either execute the request and either render something or return an error:

 makeRequest('/api/something').then(function(body) { // Render the response body }).catch(function(err) { // Handle the API error object }); 

Hooks available during event handling

There are several hooks available for each event.



Usage example:

 Actions.statusUpdate.preEmit = function() { console.log(arguments); }; Actions.statusUpdate.shouldEmit = function(value) { return value > 0; }; Actions.statusUpdate(0); Actions.statusUpdate(1); //   : 1 


You can define hooks directly when declaring actions:

 var action = Reflux.createAction({ preEmit: function(){...}, shouldEmit: function(){...} }); 

Reflux.ActionMethods

If you need to perform any method on the objects of all actions, for this you can extend the `Reflux.ActionMethods` object, which is automatically mixed into all actions when created.

Usage example:

 Reflux.ActionMethods.exampleMethod = function() { console.log(arguments); }; Actions.statusUpdate.exampleMethod('arg1'); // : 'arg1' 

Creating repositories

Stores are created in much the same way as the ReactJS (`React.createClass`) component classes - by passing an object defining storage parameters to the` Reflux.createStore` method. All event handlers can be initialized in the `init` method of the repository by calling the` `listenTo` 'own repository method.

 //   var statusStore = Reflux.createStore({ //   init: function() { //    statusUpdate this.listenTo(statusUpdate, this.output); }, //    ,   output: function(flag) { var status = flag ? 'ONLINE' : 'OFFLINE'; //     ,     this.trigger(status); } }); 

In the example above, when the action is called `statusUpdate`, the storage method` output` will be called with all the parameters passed during the sending. For example, if an event was sent using the `statusUpdate (true)` call, the `true` flag will be passed to the` output` function. After that, the repository itself will work as an action and transfer to its subscribers as `status` data.

Since the repositories themselves are the initiators of sending events, they also have the `preEmit` and` shouldEmit` hooks.

Reflux.StoreMethods

If it is necessary to make a certain set of methods available at once in all storages, for this you can extend the object `Reflux.StoreMethods`, which is mixed into all storages when they are created.

Usage example:

 Reflux.StoreMethods.exampleMethod = function() { console.log(arguments); }; statusStore.exampleMethod('arg1'); //  : 'arg1' 

Impurities (mixins) in storage

Just as you add objects to React components, you can add them to your repositories:

 var MyMixin = { foo: function() { console.log('bar!'); } } var Store = Reflux.createStore({ mixins: [MyMixin] }); Store.foo(); //  "bar!"   


Impurity methods are available in the same way as the native methods declared in the repositories. Therefore, `this` from any method will point to the repository instance:

 var MyMixin = { mixinMethod: function() { console.log(this.foo); } } var Store = Reflux.createStore({ mixins: [MyMixin], foo: 'bar!', storeMethod: function() { this.mixinMethod(); //  "bar!" } }); 


Conveniently, if several impurities are mixed into the repository, which determine the same methods of the event life cycle (`init`,` preEmit`, `shouldEmit`), all these methods will be guaranteed to be invoked (as in ReactJS itself).

Convenient subscription to a large number of actions

Since the repository init method usually subscribes to all registered actions, the repositories have a `listenToMany` method that takes as an argument an object with all the events created. Instead of the following code:

 var actions = Reflux.createActions(["fireBall","magicMissile"]); var Store = Reflux.createStore({ init: function() { this.listenTo(actions.fireBall,this.onFireBall); this.listenTo(actions.magicMissile,this.onMagicMissile); }, onFireBall: function(){ // whoooosh! }, onMagicMissile: function(){ // bzzzzapp! } }); 


... you can use this:

 var actions = Reflux.createActions(["fireBall","magicMissile"]); var Store = Reflux.createStore({ init: function() { this.listenToMany(actions); }, onFireBall: function(){ // whoooosh! }, onMagicMissile: function(){ // bzzzzapp! } }); 


Such code will add handlers for all actions `actionName`, for which there is a corresponding storage method` onActionName` (or `actionName` if you prefer). In the example above, if the `actions` object also contained an Κ»iceShard` action, it would simply be ignored (since there is no corresponding handler for it).

The `listenables` property

To make it even more convenient for you, you can assign an object with actions to the `listenables` storage property, it will be automatically transferred to` listenToMany`. Therefore, the example above can be simplified to this:

 var actions = Reflux.createActions(["fireBall","magicMissile"]); var Store = Reflux.createStore({ listenables: actions, onFireBall: function(){ // whoooosh! }, onMagicMissile: function(){ // bzzzzapp! } }); 


The `listenables` property can be an array of similar objects. In this case, each object will be passed to `listenToMany`. This allows you to conveniently do the following:

 var Store = Reflux.createStore({ listenables: [require('./darkspells'),require('./lightspells'),{healthChange:require('./healthstore')}], //       }); 

Subscription to repositories (handling events sent by repositories)

In your component, you can subscribe to event handling from the repository like this:

 //     var statusStore = Reflux.createStore({ //   init: function() { //    statusUpdate this.listenTo(statusUpdate, this.output); }, //  output: function(flag) { var status = flag ? 'ONLINE' : 'OFFLINE'; //    this.trigger(status); } }); //   ,       function ConsoleComponent() { //    statusStore.listen(function(status) { console.log('status: ', status); }); }; 


 var consoleComponent = new ConsoleComponent(); 


We send events along the chain using the `statusUpdate` action object as functions:

 statusUpdate(true); statusUpdate(false); 


If you do everything as stated above, the output should be like this:

  status: ONLINE
 status: OFFLINE 


An example of working with React components

Subscribing to actions in the React component can be done in the ` componentDidMount` [lifecycle method] () method, and unsubscribed in the `componentWillUnmount` method like this:

 var Status = React.createClass({ initialize: function() { }, onStatusChange: function(status) { this.setState({ currentStatus: status }); }, componentDidMount: function() { this.unsubscribe = statusStore.listen(this.onStatusChange); }, componentWillUnmount: function() { this.unsubscribe(); }, render: function() { //   } }); 

Impurities for convenient operation inside React components

Since components need to constantly subscribe / unsubscribe from events at the right moments, for ease of use, you can use the admixture `Reflux.ListenerMixin`. Using it, the example above can be rewritten as:

 var Status = React.createClass({ mixins: [Reflux.ListenerMixin], onStatusChange: function(status) { this.setState({ currentStatus: status }); }, componentDidMount: function() { this.listenTo(statusStore, this.onStatusChange); }, render: function() { // render specifics } }); 


This impurity makes the `listenTo 'method available for calling inside the component, which works in the same way as the repository method of the same name. You can use the `listenToMany` method.

Using Reflux.listenTo

If you do not use any specific logic regarding `this.listenTo ()` inside `componentDidMount ()`, you can use the call `Reflux.listenTo ()` as an impurity. In this case, `componentDidMount ()` will be automatically configured as required, and you will get an admixture of `ListenerMixin` in your component. Thus, the example above can be rewritten as:

 var Status = React.createClass({ mixins: [Reflux.listenTo(statusStore,"onStatusChange")], onStatusChange: function(status) { this.setState({ currentStatus: status }); }, render: function() { //    `this.state.currentStatus` } }); 


You can insert multiple calls of `Reflux.listenTo` inside the same` mixix` array.

There is also `Reflux.listenToMany` which works in the same way, allowing you to use` listener.listenToMany`.

Using Reflux.connect

If all you need is to update the state of the component when you receive data from the repository, you can use the expression `Reflux.connect (listener, [stateKey])` as an admixture of the ReactJS component. If you pass the optional `stateKey` key there, the state of the component will be automatically updated using` this.setState ({: data}) `. If `stateKey` is not passed, a call will be made to` this.setState (data) `. Here is an example above, rewritten to reflect new features:

 var Status = React.createClass({ mixins: [Reflux.connect(statusStore,"currentStatus")], render: function() { // render using `this.state.currentStatus` } }); 


Using Reflux.connectFilter


`Reflux.connectFilter` can be used exactly the same as` Reflux.connect`. Use `connectFilter` as an impurity in case you need to transfer only a part of the storage state to the component. For example, a blog written using Reflux is likely to keep all publications in the repository. And on the page of a separate post, you can use `Reflux.connectFilter` to filter posts.

 var PostView = React.createClass({ mixins: [Reflux.connectFilter(postStore,"post", function(posts) { posts.filter(function(post) { post.id === this.props.id; }); })], render: function() { // ,  `this.state.post` } }); 

Handling change events from other repositories

The repository can subscribe to changes in other repositories, allowing you to build data transfer chains between repositories to aggregate data without affecting other parts of the application. A repository can subscribe to changes that occur in other repositories using the `listenTo` method, just as it does with action objects:

 //  ,    ,   statusStore var statusHistoryStore = Reflux.createStore({ init: function() { //       this.listenTo(statusStore, this.output); this.history = []; }, //   output: function(statusString) { this.history.push({ date: new Date(), status: statusString }); //    this.trigger(this.history); } }); 

Additional features


Using the alternative event management library

Don't like the `EventEmitter` provided by default? You can switch to using any other, including the one built into Node like this:

 //         Reflux.setEventEmitter(require('events').EventEmitter); 


Using an alternative library of promises

Do not like the library that implements the functionality of promis, provided by default? You can switch to using any other (for example, Bluebird like this:

 //        Reflux.setPromise(require('bluebird')); 


Keep in mind that promises in RefluxJS are created by calling `new Promise (...)`. If your library uses factories, use the `Reflux.setPromiseFactory ()` call.

Use of promis factory

Since most of the libraries for working with promises do not use constructors (`new Promise (...)`), there is no need to set up a factory.

However, if you use something like `Q` or some other library that uses the factory method to create promises, use the` Reflux.setPromiseFactory` call to specify it.

 //       Reflux.setPromiseFactory(require('Q').Promise); 

nextTick

, . , `setTimeout` ( `nextTick`) RefluxJS.

(`setTimeout`, `nextTick`, `setImmediate` ..) .

 // node.js env Reflux.nextTick(process.nextTick); 


, `setImmediate` `macrotask`


Reflux API `join`, , . , `waitFor` Flux Facebook.


, `join()` . , `join`.

`join`, :



:

 joinXyz(...publisher, callback) 


As soon as `join ()` is executed, all restrictions related to it will be removed and it will be able to work again if the publishers send events to the chain again.

Using instance methods for event management

All objects that use the listener API (repositories, React components that have mixed with `ListenerMixin`, or other components that use` ListenerMethods`) get access to four variants of the `join` method that we talked about above:

 var gainHeroBadgeStore = Reflux.createStore({ init: function() { this.joinTrailing(actions.disarmBomb, actions.saveHostage, actions.recoverData, this.triggerAsync); } }); actions.disarmBomb("warehouse"); actions.recoverData("seedyletter"); actions.disarmBomb("docks"); actions.saveHostage("offices",3); // `gainHeroBadgeStore`            `[["docks"],["offices",3],["seedyletter"]]` 


Using static methods

Since the use of the `join` methods, and then sending events to the chain is common for the repository, all the join methods have their static equivalents in the` Reflux` object, which return the repository object subscribed to the specified events. Using these methods, the example above can be rewritten as:

 var gainHeroBadgeStore = Reflux.joinTrailing(actions.disarmBomb, actions.saveHostage, actions.recoverData); 


Send a default state using the listenTo method

The `listenTo` function provided by the repository and` ListenerMixin` has a third parameter, which can be a function. This function will be called at the time of registration of the handler with the result of calling `getInitialState` as parameters.

 var exampleStore = Reflux.createStore({ init: function() {}, getInitialState: function() { return "-   "; } }); //      this.listenTo(exampleStore, onChangeCallback, initialCallback) // initialCallback      "-   " 

Remember the `listenToMany` method? If you use it with other repositories, it also supports `getInitialState`. The data returned by this method will be passed to the normal handler, or to the `this.onDefault` method if it exists.

Colophon


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


All Articles