While I can not give an authoritative answer, I would say yes, you should use classes in the state. I consider this good practice in line with the Separation of Concern design principle. However, you should not have methods on those classes to call directly.
tl;dr;
- use classes as DTOs or models
- keep state clean of methods, but you might add some "locally calculated state" if needed.
- mutate data as much as you need in the mutations, even mutating some "locally calculated state" from above, if needed.
- if needed, pick specific data with specific getters, possibly having many getters for the same underlying data
- if needed, implement separate code outside the state, getters and mutations instead of methods directly in those.
Example
In my application, using vuex and typescript, I have written a "api-model" that represents the various parts of my API responses and requests and some as unitTree.ts
helper class code that is responsible for the handling of the enterpriseUnitsTree
part of the state, which is a local subset of the caseFullData
.
Here's how I do it in the state.ts (simplified):
import { ICaseFullDataViewModel, CaseFullDataViewModel, CaseApprovalDto, ICaseApprovalDto } from '@/api-model';
import { IEnterpriseUnitDataObjectTree } from '../code/unitTree';
export interface IEntryState {
caseValidationMessages: string[];
caseFullData: ICaseFullDataViewModel;
enterpriseUnitsTree: Array<IEnterpriseUnitDataObjectTree>;
caseApproval: ICaseApprovalDto;
}
export const state: IEntryState = {
caseValidationMessages: null,
caseFullData: new CaseFullDataViewModel(),
enterpriseUnitsTree: null,
caseApproval: new CaseApprovalDto()
};
As for getting data, I would recommend having a complete set of getters for each aspect you need. It keeps the state clean as really to be only a state, not a business/use case in itself. See below some getters for parts of the above data. For example, I am using external code in unitTree.ts
to extract a specific item of enterpriseUnitsTree
from the store.
Here's my code in getters.ts (simplified):
import { IRootState } from '@/store';
import { GetterTree } from 'vuex';
import { unitTree } from '../code/unitTree';
import { IEntryState } from './state';
export const CASE_ID = 'caseId';
export const CASE_FULL_DATA = 'caseFullData';
export const UNITS_SORTED_AS_TREE = 'unitsSortedAsTree';
export const FIRST_UNIT = 'firstUnit';
export const getters: GetterTree<IEntryState, IRootState> = {
[CASE_ID]: state => state.caseFullData?.caseId,
[CASE_FULL_DATA]: state => state.caseFullData,
[UNITS_SORTED_AS_TREE]: state => {
return state.enterpriseUnitsTree;
},
[FIRST_UNIT]: state => () => {
const indexMap = unitTree.getIndexMap(state.enterpriseUnitsTree);
const firstUnitIdentifier = indexMap.get(0);
return firstUnitIdentifier;
}
//....
};
In the mutations.ts
, I do mutate the the enterpriseUnitsTree
, as needed, when receiving a commit the the respective data:
Here's my code in mutations.ts (simplified):
import { MutationTree } from 'vuex';
import {
ICaseFullDataViewModel
} from '@/api-viewmodel';
import { IEntryState } from './state';
import { unitTree } from '../code/unitTree';
export const MAPCASEFULLDATA = 'mapCaseFullData';
//....
export const mutations: MutationTree<IEntryState> = {
[MAPCASEFULLDATA](state, value: ICaseFullDataViewModel) {
state.caseFullData = value;
//... some sorting and tree building using code from unitTree
state.enterpriseUnitsTree = sortedEnterpriseUnits;
},
//.......
};