Flue. Another flux library
This time for Vue.js
We all know that frameworks and libraries help the developing of scalable web applications. With React, the Flux pattern became widely used to maintain the state in a predictable and convenient way.
We have seen tonnes of Flux implementation, the most famous one is Redux. It can be used with Vue, but the state is not reactive by design so we lost a huge part of Vue’s advantages. The native state management library is Vuex, developed by the Vue’s creator. It is a great tool, but honestly I do not like its implementation.
So I thought that maybe, as a side project, I could try to create a new one. I collect all my fantasy and I called it: Flue (Flux + Vue).
One of the main goals was to provide a pleasant object oriented implementation that can support async actions out of the box. Also, we are going to see it later, Actions and Middlewares for Redux are fully compatible.
Overview
First of all, you can find it here:
https://github.com/FrancescoSaverioZuppichini/Flue
As in all Flux implementation, the basic component is the Store. It has a state and it can dispatch actions and switch behaviour based on their type. One main different from other libraries is that, with Flue, each store can be added to a global Store in order to share its state with the system. This specific store that holds the global state is called the SuperStore.
Store
I love to code. So, let’s immediately make an example. This is the HelloWorld store
import { Action, Store } from 'flue-vue'
class HelloWorld extends Store {
constructor() {
super()
this.state.text = ""
} reduce(action) {
this.reduceMap(action, {
'HELLO_WORLD': ({text}) => this.state.text = text
})
} actions(ctx) {
return {
helloWorld() {
ctx.dispatchAction('HELLO_WORLD', {text: 'Hello World'})
}
}
}
}
const helloWorldStore = new HelloWorld()export default helloWorldStore
In the constructor we explicit initialise the state. In this way, Flue can later on fetch it and make it reactive using a Vue vm. Otherwise Vue.set() must be called in order to ensure its reactivity when a new property is added.
The reduce function is the classic reducer from Redux. It takes an action as parameter and does something based in its type. You can notice that we do not use the classic switch that you may be used to see. The Store class expose a function called reduceMap that allows the developers to write in a more concise way the old school switch case. It uses a map where the keys are the actions type and the values are the function. The code can be rewrite like this:
reduce(action) {
switch (action.type) {
case 'HELLO_WORLD':
this.state.text = action.payload.text
break;
default:
}
Classic reducer function can be used, for instance
const testReducer = (action) => {
console.log(action)
}
Is valid and will log the actions.
The actions function returns an object of functions. Each of them dispatch a specific action, or more of them. This function provides the store’s API, in other word, what we can do from that store. As parameter the store itself is passed for fast access, but we could also write:
actions() {
const dispatch = this.dispatch // we are in the store scope return {
helloWorld() {
dispatch(new Action('HELLO_WORLD', {text: 'Hello World'}))
}
}
}
You can always put your actions in other files and import them.
import {ACTION} from './actions.js`{ //inside a vue component this.$store.dispatch(ACTION)}
Actions provider can be created and added later to the SuperStore
const apiActionProvider = (ctx) => {
return {
getMeFromAPI() {
ctx.dispatch(new Action("GET_ME_FROM_API"))
}
}
}SuperStore.addActions(apiActionProvider)
Similarly to Redux (it is actually the same code!), you can subscribe to the store in order to listen for updates
const unsubscribe = SuperStore.subscribe((store)=> {
console.log(store.state)
})
For Vue this is not required since the state is reactive and every changes will trigger a re-render.
SuperStore
In order to make the ecosystem works each store or reducer must be added to the SuperStore.
import HelloWorldStore from './HelloWorldStore.jsSuperStore.addStore(HelloWorldStore) // add a Store instance
SuperStore.addStore(testReducer) // a function reducer
// or
SuperStore.addStores([HelloWorldStore,testReducer]) // add multiple at ones
The SuperStore is the container of all the stores state and actions. Actions can be called directly from the Superstore instance as well as the state.
SuperStore.actions.helloWorld()
SuperStore.state
When a Store is added to the SuperStore its state is passed to a Vue vm in order to fetch it and make it reactive. The state is not immutable! Since we rely on reactivity.
Each Store has a pointer stored by constructor name for convenience
SuperStore.HelloWorldStore //point to the create HelloWorldStore
The SuperStore can be access from every Vue Component with the reference $store
Actions
Actions must contain a type field and a payload field. You can use the Action
class or classic object
import {Action} from 'flue-vue'const fooAction = new Action('FOO',{foo:'foo'})
const fooAction = {type:'FOO',payload:{foo:'foo'})
They are equivalent. You can also use your custom Actions with custom fields, just remember to always provide a type.
If you for some reason do not want to use the payload, you can customise the reduceMap function by passing the key to the action’s data.
this.reduceMap(action,{//map},'data') // action.data will be passed to all the functions
For consistency I suggest to subclass the Action class
import {Action} from 'flue-vue'class MyAction extends Action { ... }
Middleware
Redux middleware are supported. Redux-thunk is not necessary since we are using a promise based dispatcher and async actions are supported natively.
Let’s add a the classic redux-logger
import logger from 'redux-logger'
import {SuperStore} from 'flue-vue'SuperStore.applyGlobalMiddleware([logger]) //applied to each store
SuperStore.applyMiddleware(HelloWorld,[logger]) //applied only to a specific store
We have tried also redux-api-middleware and it works as a charm!.
This is possible due to the getState function in the Store class that returns a deep copy of the current state in order to provide to the middleware the same API that Redux has.
Credits
I took lots of inspiration from Redux (https://github.com/reactjs/redux) and from Vuex (https://github.com/vuejs/vuex).
Conclusion
Flue is not production ready but I have used without any problems for my bachelor project and other side projects like https://beautifulquotations.herokuapp.com/ and it works without any problems. I am looking forward to your feedbacks. Thank you for reading
P.S
This is my first post, so guys any advice is appreciated.
Francesco Saverio