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

')
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
- Examples
- Installation
- Using
- Developments
- Storage
- Use with ReactJS components
- Details
- Epilogue
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:
- There are action games
- There are data warehouses
- Data moves only in one direction.
Differences from Flux
RefluxJS is an improved version of the Flux-concept, more dynamic and friendlier to functional reactive programming:
- The dispatcher of events (dispatcher), which in Flux was a singleton, is not in RefluxJS. Instead, each event (action) is its own dispatcher.
- Since actions can be subscribed to, stores can do this directly without using cumbersome switch statements to separate flies from cutlets.
- Repositories can subscribe to other repositories. That is, it becomes possible to create storages that aggregate and process data in the map-reduce style.
- The waitFor () call was deleted. Instead, data processing can be done sequentially or in parallel.
- Repositories that aggregate data (see above) can subscribe to other repositories, processing messages sequentially
- You can use the join () method to wait for other events to be processed.
- Special action factors are not needed at all, since RefluxJS action games are functions that transmit the necessary data to everyone who subscribes to them.
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);
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" ]);
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`.
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`:
- `promise` - As a parameter, it waits for the object of the promise and binds the call` completed` and `failed` to this promise using` then () `and` catch () `.
- `listenAndPromise` - As a parameter expects a function that returns a promise object. It (the object of the promise that the function returned) will be called when the event occurs. Accordingly, the `then ()` and `catch ()` promises are automatically called 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:
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) {
Hooks available during event handling
There are several hooks available for each event.
- `preEmit` - called before the action sends the event information to subscribers. As arguments, the hook gets the arguments used when sending the event. If the hook returns anything other than undefined, the return value will be used as parameters for the `shouldEmit` hook and will replace the data sent.
- `shouldEmit` - called after` preEmit`, but before the action sends the event information to subscribers. By default, this handler returns true, which allows data to be sent. You can override this behavior, for example, to check the arguments and decide whether the event should be sent in a chain or not.
Usage example:
Actions.statusUpdate.preEmit = function() { console.log(arguments); }; Actions.statusUpdate.shouldEmit = function(value) { return value > 0; }; Actions.statusUpdate(0); Actions.statusUpdate(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');
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.
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');
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();
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();
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(){
... you can use this:
var actions = Reflux.createActions(["fireBall","magicMissile"]); var Store = Reflux.createStore({ init: function() { this.listenToMany(actions); }, onFireBall: function(){
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(){
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 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() {
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() {
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() {
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() {
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:
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:
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:
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.
nextTick
, . , `setTimeout` ( `nextTick`) RefluxJS.
(`setTimeout`, `nextTick`, `setImmediate` ..) .
,
`setImmediate` `macrotask`Reflux API `join`, , . , `waitFor` Flux Facebook.
, `join()` . , `join`.
`join`, :
- `joinLeading`: .
- `joinTrailing`: .
- `joinConcat`: .
- `joinStrict`: .
:
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);
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 "- "; } });
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