State management for Vue:
- Compatible with Vue Devtools
- Type-safe mutations
- Reactive approach (based on RxJS)
- Inspired by Vuex
Install RxJS as a peer dependency: npm install rxjs --save
npm install vuex-rxjs --save
yarn add vuex-rxjs
First, make sure VuexRxJS is properly installed/registered:
main.ts:
import {VuexRxJS} from "vuex-rxjs";
VuexRxJS(Vue); // Install VuexRxJS
new Vue({
render: h => h(App),
}).$mount('#app')
Then, define your application state and your first store:
store.ts:
import {Store} from "vuex-rxjs/dist/store";
/**
* Define the state:
*/
class State {
counter: number = 0; // initial value for counter
}
/**
* Define your store:
*/
class SimpleStore extends Store<State> {
/**
* Mutation:
*/
increment() {
this.commit({
type: 'INCREMENT_COUNTER',
payload: state => ({
counter: state.counter + 1
})
});
// NOTE: the payload function never alters the state;
// instead, it returns a new state object
}
}
/**
* Instantiate your store and export it:
*/
export const store = new SimpleStore(new State());
Now you can use your store in all of your components:
Component.vue:
<template>
<div id="app">
<p>Count: {{count}}</p>
<button @click="addOne()">Add 1</button>
</div>
</template>
<script lang="ts">
import {Component, Vue} from 'vue-property-decorator';
import {store} from "./store";
@Component
export default class App extends Vue {
// Bind a state property to a component property:
count: number = store.bind(state => state.counter);
// Call a store method:
addOne() {
store.increment();
}
}
</script>
🍻🎊 Done! 🎉🥂
In complex applications, you will probably want to split your state into different parts, like so:
Rootstate
└ auth
└ profile
└ article
└ ...
Modules help you do exactly this. First, define your application state (root state):
root.store.ts:
/**
* Define the root state:
*/
class RootState {
auth = new AuthState();
profile = new ProfileState();
article = new ArticleState();
}
export const rootStore = new Store(new RootState());
profile.store.ts:
/**
* Define the profile state:
*/
class ProfileState {
username: string = '';
}
/**
* Define your module store:
*/
class ProfileStore extends Module<ProfileState, RootState> {
setUsername(newName: string) {
this.commit({
type: 'SET_USERNAME',
payload: profile => ({
username: newName
})
});
}
}
export const profileStore = new ProfileStore(
rootStore,
rootState => rootState.profile, // mapping from the root state to the profile state
(profile, rootState) => rootState.profile = profile // mapping from the profile state to the root state
);
Component.vue:
<template>
<div id="app">
<p>Your name: {{username}}</p>
<button @click="changeName()">I'm Bob now!</button>
</div>
</template>
<script lang="ts">
import {profileStore} from "./profile.store";
@Component
export default class App extends Vue {
username: string = profileStore.bind(profile => profile.username);
changeName() {
profileStore.setUsername('Bob');
}
}
</script>
Store:
class Store<RootState> {
constructor(initialState: RootState);
// in case you need to directly subscribe to the state:
state$: Observable<RootState>;
// bind a state property to a component property:
bind<PropertyType>(mapping: (state: RootState) => PropertyType): PropertyType;
// mutate the state:
commit(mutation: Mutation<RootState>): void;
// get the current state and invoke mutations (or do other stuff) based on it:
dispatch(action: Action<RootState>): Promise<void>;
}
Module:
class Module<ModuleState, RootState> {
constructor(
parentStore: IStore<RootState>,
stateMap: (state: RootState) => ModuleState,
dispatchMap: (moduleState: ModuleState, rootState: RootState) => void
);
// in case you need to directly subscribe to the module state:
state$: Observable<ModuleState>;
// bind a module state property to a component property:
bind<PropertyType>(mapping: (state: ModuleState) => PropertyType): PropertyType;
// mutate the module state:
commit(mutation: Mutation<ModuleState, RootState>): void;
// get the current module state and invoke mutations (or do other stuff) based on it:
dispatch(action: Action<ModuleState, RootState>): Promise<void>;
}
Mutations and Actions:
interface Mutation<State, RootState=State> {
type: string;
payload: Payload<State, RootState>;
}
type Payload<State, RootState=State> = (state: State, rootState: RootState) => State;
type Action<State, RootState=State> = (state: State, rootState: RootState) => Promise<void>;
- Add tests
- Add module example