Lightweight Vue 3 composition API-compatible store pattern library with built-in undo/redo functionality.
Lightweight Vue 3 composition API-compatible store pattern library. Offers a simple alternative that is on par with VueX in terms of features:
readonly
to prevent modifications via accidental referencescomputed
and regular functionsnpm install --save @korijn/vue-store
This example briefly demonstrates a locally scoped store with out-of-the-box undo/redo functionality. Read on to learn more about the options provided.
<template>
<div>
<p>Count: {{ state.count }}</p>
<p>
<button @click="increment">Increment</button>
<button @click="undo" :disabled="!canUndo">Undo</button>
<button @click="redo" :disabled="!canRedo">Redo</button>
</p>
</div>
</template>
<script>
import createStore from '@korijn/vue-store';
export default {
name: 'Counter',
setup() {
const store = createStore({
count: 0,
}, {
increment(state) {
state.count += 1;
},
});
return { ...store };
},
};
</script>
This library provides a single function createStore
with the following signature:
import createStore from '@korijn/vue-store';
const store = createStore(initialState, mutations);
The constructor arguments are defined as follows:
initialState
- the state object with which the store will be initialized. For example:
const initialState = {
todos: [],
};
mutations
- the mutations that you wish to expose on your store instance. These should bestate
as their first positional argument, and any options provided toconst mutations = {
addTodo(state, { text }) { state.todos.push({ text, status: 'open' }); },
};
Note: mutations cannot return anything, and must be synchronous entirely.You can create your store inside a component's setup
function if you want the
store to be locally defined, or you can do so in a plain JS module and import it from
various places accross your application to have a globally defined store instance.
The same holds true for any computed
ref's you'd like to define. These don't necessarily have to
live in a component. If you want them to be globally available, just define them in a plain JS
module and import them where you need to.
The store
object returned by createStore
provides the following keys:
state
- a reactive readonly proxy tosetup
function to usewatch
, watchEffect
and computed
setup() {
const store = createStore(...);
return {
state: store.state,
};
}
commit(type, options)
- use this function to commit mutations to the store. type
is theoptions
passes any relevant data to the mutation callback. For example:
store.commit('addTodo', { text: 'Buy groceries' });
...mutators
- as a convenience shorthand, you can call mutations directly as functions on thecommit
example above:
store.addTodo({ text: 'Buy groceries' });
undo()
, redo()
- whenever you commit a mutation to the store, immer.js is used to generatebool
to indicate whether orundo()
returns false
iff the store isredo()
returns false
iff the store is already in its finalconst success = store.undo();
canUndo
, canRedo
- these are two plain computed
refs, which will tell you whether or not<button @click='undo' :disabled="!canUndo">Undo</button>
past
, future
- two more reactive readonly objects, each holding the store's mutation historyconst undoStepsLeft = computed(() => store.past.length);
You can see a Todo MVC application using a globally instanced store at the following URL:
https://korijn.dev/vue-store-todo-app/index.html
Source code:
https://github.com/Korijn/vue-store-todo-app
In particular the following files will be of interest for your review:
/src/store.js
/src/App.vue
I wrote this library when I realized I needed a way to centrally manage state in a new Vue 3 application I was working on, and noticed that VueX didn't offer what I needed:
At some point I found the incredible immer.js
library and noticed it could generate JSON patches in both directions while running mutation
callbacks. Combining that with the rfc6902
library was the key to this library's commit
function, which works as follows:
produceWithPatches
is run on non-reactive store state, using Vue's toRaw
, so as toThis way, developers only need to write forward mutation callbacks, and since all state is plain
reactive
objects, they can leverage all the regular Vue composition API utilities such as
computed
, watch
and watchEffect
to their fullest extent.
Thank you for reading this and checking out my library. If you have any questions, bug reports, or feature requests, feel free to create an issue.