Fflux
Flux-based architecture library with immutable data support
Install / Use
/learn @Kureev/FfluxREADME
fflux.js [deprecated, use redux instead]
Contents:
- What is FFlux?
- Examples
- Roadmap
- Installation
- Application
- Dispatcher
- Action Creators
- Action Scope
- Stores
- Data Scope
- View layer
Examples:
What is FFlux?
- Simple way to use flux architecture
- Minimum API
- Two types of stores: mutable & immutable
- Isomorphic friendly (no singletons)
- 100% test coverage
- Detailed error messages (using facebook's
invariant) - Modular: use only parts you need
Roadmap
- [ ] Finalize the API
- [X] Create a data scope for simple data (de|re)hydration
- [X] Create application abstraction
- [X] Create action scope abstraction
- [X] Separate stores to mutable and immutable
- [ ] Write "Getting Started"
- [X] Make an example of isomorphic app
- [ ] Make an example for Riot.js
- [ ] Find a way to avoid using mixins
Installation
npm
npm install fflux
bower
bower install fflux
Application
Application is an entry point for fflux.
var Application = require('fflux/src/Application');
var app = new Application();
Also, you can use config object to pre-set application enviroment:
var app = new Application({
stores: {
list: new ListStore(),
wishlist: new WishlistStore()
},
actions: {
list: listActions,
wishlist: wishlistActions
}
});
Once you got app instance, you can use it to access storesScope and actionsScope by using app.stores() or app.actions(). Also you're able to get access to specific store or action directly: app.stores('some') or app.actions('some').
Dispatcher
FFlux dispatcher extends facebook's dispatcher implementation.
var Dispatcher = require('fflux/src/Dispatcher');
var dispatcher = new Dispatcher();
dispatcher.dispatch('SOME_ACTION', payload);
Dispatcher API
-
register - register store in dispatcher<br> After registration, store will receive dispatcher's actions<br>
dispatcher.register(store); -
unregister - unregister store from the dispatcher<br> Store wouldn't receive actions any more.<br>
dispatcher.unregister(store); -
dispatch - dispatch action to the stores:<br>
dispatcher.dispatch(actionName, actionPayload);` -
waitFor - wait for another store to process action first
var someStore = FFlux.ImmutableStore(); dispatcher.waitFor([someStore]);
Action Creators
Action Creators are commonly used to fetch/post data. All async stuff should happend here.
var request = require('superagent');
var ActionCreatorExample = {
/**
* Fetch data from server
* @param {Function} dispatch
* @param {Object} criteria
*/
fetchData: function(dispatch, criteria) {
request
.get('/some/url')
.end(function(res) {
dispatch('FETCH_DATA', res);
});
},
/*
* Post data to the server
* @param {Function} dispatch
* @param {Object} data
*/
postData: function(dispatch, data) {
request
.post('/some/url')
.send(data)
.end(function(err, res) {
if (err) {
dispatch('FETCH_DATA_ERROR', err);
}
dispatch('POST_DATA', res);
});
}
};
In this example I use superagent as isomorphic library for AJAX calls. As you probably mentioned, in every function we got dispatch as a first parameter. That's done to simplify your life. Every time you register action creator in the action scope, this dependency injection managed for you automaticly.
Action Scope
The point is, that as like with the stores, it's handy to keep all your action creators inside your application instance. So, to provide you consistant interface (with data scope), action scope has been created. Action Scope isn't aimed to be used outside from the application
var app = new Application({
actions: {
scope: scopeActions
}
});
app.actions(); // ActionScope instance
Action Scope API
-
register - register action creator object in the scope
app.actions().register('otherScope', otherScopeActions); -
unregister - unregister action creator object by name
app.actions().unregister('otherScope'); -
get - register action creator object in the scope
app.actions().get('scope'); // scope action creator
Stores
In fflux, you can use mutable and immutable stores. If you want to work with store's state as native javascript object - you should use mutable store. If you prefer immutable structures, immutable stores - is your choice. Both stores have the same API:
Store API
-
setState - merge current state with the one provided<br> Hint: if you're using mutable stores, every
setStatewill emitchangeevent. In the case of immutable stores,changeevent will be emitted only if new state is different from the current one.<br>store.setState({ key: value }); -
replaceState - replace current state with a given one<br>
store.replaceState({ key: value }); -
registerAction - register action handler<br>
store.registerAction(actionName, actionHandler); -
unregisterAction - unregister action handler<br>
store.unregisterAction(actionName);
Mutable Store
Mutable store is a basic store you have in fflux:
var store = new FFlux.MutableStore({
/**
* In this property we declare list
* of the actions we're interested in.
*/
actions: {
'SOME_ACTION': 'someMethod'
'OTHER_ACTION': 'otherActionHandler'
},
/**
* Get initial state
* Works the same way as in React
* @return {Any} Initial state
*/
getInitialState: function() {
return {};
}
/**
* Handler for SOME_ACTION
* @param {Object} data Payload
* @return {Void}
*/
someActionHandler: function(data) {
this.setState(data);
},
/**
* Handler for OTHER_ACTION
* @param {Object} data Payload
* @return {Void}
*/
otherActionHandler: function(data) {
this.setState(data);
}
});
Every handler could be a method name of the store instance or a standalone function. In every action handler you can use waitFor method as described here:
{
/**
* Some action's handler
* @param {Object} data Payload
* @return {Void}
*
* @description For invoke some function(s) only *after* other store
* process action, we need to use `waitFor` method
*/
someMethod: function(data) {
/**
* If we need to be sure, that some function will be called
* only after `storage` store would process the action
*/
dispatcher.waitFor([storage]);
}
}
You can register/unregister action handlers dynamically after store initialization:
var store = new FFlux.MutableStore({...});
/**
* Action handler function
* @param {Object} data Payload
* @return {Void}
*/
function actionHandler(data) {
//...
}
/**
* Register handler `actionHandler` for action `SOME_ACTION`
*/
store.registerAction('SOME_ACTION', actionHandler);
/**
* Call `unregisterAction` for remove action handler
*/
store.unregisterAction('SOME_ACTION');
And the last, but not least: states. FFlux stores have a state like React components, so you can easily work with it using already familiar functions:
setState, getState, replaceState and getInitialState.
Immutable store
Immutable store inherits form mutable store and enhances its functionality with immutable data.
var store = new FFlux.ImmutableStore();
/*
* currentState will be empty Immutable.Map
*/
var currentState = store.getState();
/**
* Mutator for `c` key in `a.b.c` path
* @param {Array} element
* @return {Immutable.List}
*/
function mutator(element) {
return element.map(function(i) {
return i * i;
});
}
}
/*
* If we using immutable data,
* any manipulation with `currentState`
* will create a new immutable object
*/
var newState = currentState
.updateIn(['a', 'b', 'c'], mutator)
.set('b', 'new key')
.set('c', currentState.getIn(['a', 'b', 'c']));
// currentState is still the same (empty Immutable.Map)
store.replaceState(newState);
Any store state operation (e.g. setState or replaceState) will trigger change event only in the cases when previous state isn't equal to the new one.
Data Scope
Data Scope is used in the fflux applications for grouping stores and providing simple interface to manage them. Fo
