I am in the process of lessening the bulk of a Vuex store module that has grown too big and overly complex. Our state management system is built with Vuex using JS classes where we have a base module that is extended to multiple other more specific modules. I am currently refactoring one of those more ‘specific’ modules - this module handles the state of a couple views containing a handful of components. I am attempting to extend this module the same as all of our other modules and use spread syntax to bring in the existing state properties…
export default class ChildModule extends BaseModule {
constructor() {
super()
this.state = {
...this.state
}
this.getters = {
...this.getters
}
}
// ** the same syntax for actions and mutations
// logging rootState in an action inside GrandchildModule correct values from ChildModule
}
the only difference in the working module vs the broken module is the class that’s being extended…
export default class GrandchildModule extends ChildModule {...
for some reason that I haven’t figured out yet the GrandchildModule
is not inheriting the state from the ChildModule
properties that require fetching data from the api - properties that do not require api access are behaving correctly - I have been unsuccessful using getters in the GrandchildModule
which I expected to be reactive to fetched data?
I’m hoping someone has some insight on how I can make this work or tell me what i’m doing wrong
update: found this on reactivity that i'm currently investigating - not 100% that this is my issue but it could be?
example structure - stackblitz with js classes
The first problem with the stackblitz you shared is that in each of the classes (except BaseClass
) you spread state
in constructor
. And state
is not defined. You probably meant this.state
:
export default class ChildClass extends BaseClass {
constructor() {
super();
this.state = {
...this.state,
name: 'ChildClass',
};
}
}
Secondly, in index.js
you're attempting to display GreatGrandchild.id
which, again, doesn't exist. GreatGrandchild
is a JavaScript class declaration (which, technically, is a function with a single property: the name
- the actual name of the class), so it doesn't have an id
. Not even its instances (in your case, timmy
) do not have an id
. But they do have a state.id
.
For a better understanding of how JavaScript classes work, I recommend MDN.
If you want to reference the inherited state.id
(from BaseClass > ChildClass > GrandchildClass > GreatGrandchild
), you could use:
const timmy = new GreatGrandchild();
console.log(timmy.state.id); // output: 0
See it working here.
From the example you posted it's unclear how your code is related to vuex
. Or vue
, for that matter. You might want to provide a minimal runnable example with what you have.
However, on general principles, I would not recommend extending Vuex stores. Once a store has been instantiated, new members added to its state
, actions
, mutations
or getters
will not be reactive. If you're determined to take that route, this question might help.
What could work for your case is a BaseClass
which doesn't actually create a store, but holds the common store props and then have different store classes which actually create stores after merging the base class's props with their own. Something along these lines:
class BaseStore {
constructor() {
// place here what you want defined on all stores
this.state = {
// ...
};
this.actions = {
// ...
};
this.getters = {
// ...
};
this.mutations = {
// ...
};
}
}
class SomeStore extends BaseStore {
constructor({ state = {}, mutations = {}, actions = {}, getters = {} }) {
super();
return new Vuex.Store({
state: {
...this.state,
...state,
},
actions: {
...this.actions,
...actions,
},
getters: {
...this.getters,
...getters,
},
mutations: {
...this.mutations,
...mutations,
},
});
}
}
class SomeOtherStore extends BaseStore {
constructor({ state = {}, mutations = {}, actions = {}, getters = {} }) {
super();
return new Vuex.Store({
state: {
...this.state,
...state,
},
actions: {
...this.actions,
...actions,
},
getters: {
...this.getters,
...getters,
},
mutations: {
...this.mutations,
...mutations,
},
});
}
}
// declare or import state, actions, getters, mutations, otherState, otherActions, otherMutations and otherGetters
const myFirstStore = new SomeStore({
state,
actions,
getters,
mutations
});
const myOtherStore = new SomeOtherStore({
state: otherState,
actions: otherActions,
mutations: otherMutations,
getters: otherGetters
})
Although the above pattern is possible, I'm skeptical about its usefulness in a real world scenario. I'm far from recommending it. The main reason is because classes are useful when you want to handle multiple instances of the same class. It's difficult to imagine a scenario where having multiple instances of the same store would be useful.
Note: I've used Vuex 3 instantiation method, because I guessed you're using Vue2 + Vuex 3. If you use Vue 3 + Vuex 4, you should replace new Vuex.Store()
with createStore()
Another note: the above example could be written without using classes. I'd argue it's a bit cleaner:
const defaultStoreProps = {
state: {
// common state
},
actions: {
// common actions
},
// etc...
}
const myFirstStore = new Vuex.Store({
state: {
...defaultStoreProps.state,
// other state
},
actions: {
...defaultStoreProps.actions,
// other actions
},
// etc...
})
const mySecondStore = new Vuex.Store({
state: {
...defaultStoreProps.state,
// other state
},
actions: {
...defaultStoreProps.actions,
// other actions
},
// etc...
})