I have two modules, let's call them core
and implementation
. How can I set up the store to enable things in core
to rely on an assumed store provided by the implementation
?
In the implementation
's store I am doing something like this:
import Core from 'core'
export default new Vuex.Store(
new Core().defaultStore
)
That will register default state, mutations, actions and getters (the setup allows the user of implementation
to extend/modify a default store provided by core
).
The problem arises in an action inside core
when it tries to access a getter in a non vue JS file.
export default class SomeClassInCore {
exampleMethod() {
// The getters will not be accessible here!
return store.getters.someKey
}
}
Is there some way to achieve "runtime" resolving of the "master" store? I was thinking about if I somehow can use window
to access the Vue instance created by the implementation
repo?
I tried doing it like this
import store from './store.js'
import Vue from 'vue'
import { CoreVueTools } from 'core'
window.Vue = Vue
window.store = store
Vue.use(CoreVueTools)
new Vue({
el: '#app',
store
})
And then accessing it like this
exampleMethod() {
return window.store.getters.someKey
}
That works, but I'm not super happy about this solution, there has to be a better way than relying on window right? Can I use another design pattern?
I'm not sure if this goes in the direction you are looking for a solution (as it does not rely on vuex), if not I hope it will at least be somewhat of an interesting read =)...
Can I use another design pattern?
You could experiment with a very simple (but not so well known yet) way to handle state management using an approach based on the Meiosis pattern (Homepage). I recently implemented this pattern with a react and a riot.js app and it worked amazingly well with so little boilerplate code compared to redux and co (plus no dependencies except for something that provides as stream, but you could implement that yourself as well!). I didn't have the chance to try it with vue yet (on my todo list), so the following code is just a quick (and reduced) rip-off from the "setup" example(s) in the repo...
The essential idea of the meiosis pattern is to rely on streams to implement a reactive loop and separate state management code from view code. A quick summary of the pattern (taken from the homepage):
(x, f) => f(x)
for function patches.Something that even works in an SO snippet =):
(Skip to the very end of this answer for the key part)
.row {
padding-bottom: 0.5rem;
}
button {
display: inline-block;
padding: 0 1rem;
color: #555;
text-align: center;
font-size: 0.75rem;
font-weight: 600;
line-height: 1.5rem;
letter-spacing: 1px;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 0.25rem;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box;
}
button:hover,
button:focus{
color: #333;
border-color: #888;
outline: 0;
}
<div id="app">
<temp />
</div>
<script src="https://cdn.jsdelivr.net/npm/flyd/flyd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script name="store">
// for convenience, let's define an initial state
const initialState = {
temperature: {
value: 22,
unit: "C"
}
};
// $update is a stream of patch functions
// the patch function passed here is doing
// nothing, it takes state and returns is,
// just for consistency...
const update = flyd.stream(state => state);
// define another stream to accumulate patches
// starting from the initial state
const getState = flyd.scan(
(state, patchFn) => patchFn(state),
initialState,
update,
);
// actions are functions that take an input
// and pass an appropriate patch function
// to the update stream
const actions = {
changeTemp: amount => update(state => ({
...state,
temperature: {
...state.temperature,
value: state.temperature.value + amount
}
})),
changeTempUnit: () => update(state => ({
...state,
temperature: {
...state.temperature,
unit: state.temperature.unit === "C" ? "F" : "C",
value: state.temperature.unit === "C"
? Math.round((state.temperature.value * 9) / 5 + 32)
: Math.round(((state.temperature.value - 32) / 9) * 5)
}
}))
};
// define global store by combining actions and getState
const store = {
getState,
actions
};
</script>
<script name="app">
new Vue({
el: "#app",
components: {
'temp': {
data() {
const initial = store.getState();
return {
temperature: initial.temperature.value,
unit: initial.temperature.unit
};
},
mounted() {
store.getState.map(s => {
this.temperature = s.temperature.value;
this.unit = s.temperature.unit;
});
},
methods: {
incTemp() {
store.actions.changeTemp(+1);
},
decTemp() {
store.actions.changeTemp(-1);
},
changeUnit() {
store.actions.changeTempUnit();
}
},
props: [],
template: `
<div class="temp-component">
<div class="row">
<button @click="incTemp">+</button>
<button @click="decTemp">−</button>
<span>The temperature is {{temperature}} °{{unit}}</span>
</div>
<div class="row">
<button @click="changeUnit">Change temperature unit</button>
</div>
</div>
`
}
}
});
</script>
The pattern is absolutely framework independent, so the setup of the store etc. can stay the same, no matter what framework you use. The key part with vue.js is how we connect the store with vue/vue-components, e.g. this:
data() {
const initial = store.getState();
return {
temperature: initial.temperature.value,
unit: initial.temperature.unit
};
},
mounted() {
store.getState.map(s => {
this.temperature = s.temperature.value;
this.unit = s.temperature.unit;
});
},
First we set parts of the global state data (or the whole global state) as return value of the data()
function of the component. And then we simply use the map function of the getState
stream to update the component state. .map()
is a reactive function of the getState stream (here provided by flyd).
PS: also check the examples repo linked above for more complex examples, including todo-mvc and realworld (and more)...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With