Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setup abstract, real time resolvable Vuex store implemented in another module?

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?

like image 791
ajthinking Avatar asked Jun 26 '19 08:06

ajthinking


1 Answers

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):

  • Start with an initial state.
  • Create an update stream of patches.
  • Patches can be Patchinko patches, Function Patches, or your own patches.
  • Create an actions object of functions that issue patches onto the update stream.
  • Create a states stream by using scan on the update stream with the initial state and (x, f) => f(x) for function patches.
  • Pass state and actions to views.

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>&nbsp;
							<button @click="decTemp">&minus;</button>&nbsp;
							<span>The temperature is {{temperature}} &deg;{{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)...

like image 198
exside Avatar answered Sep 21 '22 21:09

exside