Skip to content

Commit 9a8c0c0

Browse files
committed
refactor: switch from direct-vuex to vuex-typescript
`direct-vuex` is a typed wrapper around Vuex, but it has too much magic, and I can't figure out how to unit test it. `vuex-typescript` is more boilerplate, but less magic: we can still use `this.$store` as normal.
1 parent 743c55a commit 9a8c0c0

File tree

9 files changed

+113
-74
lines changed

9 files changed

+113
-74
lines changed

package-lock.json

Lines changed: 5 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,14 @@
1212
},
1313
"dependencies": {
1414
"core-js": "^2.6.11",
15-
"direct-vuex": "^0.9.11",
1615
"mem": "^5.1.1",
1716
"vue": "^2.6.11",
1817
"vue-class-component": "^7.2.3",
1918
"vue-property-decorator": "^8.4.1",
2019
"vue-router": "^3.1.6",
2120
"vuetify": "^2.2.19",
22-
"vuex": "^3.1.3"
21+
"vuex": "^3.1.3",
22+
"vuex-typescript": "^3.0.2"
2323
},
2424
"devDependencies": {
2525
"@commitlint/cli": "^8.3.5",

src/App.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ a {
6969
<script lang="ts">
7070
import Vue from "vue";
7171
import Component from "vue-class-component";
72-
import { getStore } from "./store";
72+
import { dispatchRestoreState, dispatchSaveState } from "./store";
7373
7474
@Component({ name: "App" })
7575
export default class extends Vue {
@@ -80,8 +80,7 @@ export default class extends Vue {
8080
}
8181
8282
public async mounted() {
83-
const store = getStore();
84-
await store.dispatch.restoreState();
83+
await dispatchRestoreState(this.$store);
8584
}
8685
}
8786
</script>

src/components/training/CharacterSelector.vue

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@
3737
import Vue from "vue";
3838
import Component from "vue-class-component";
3939
import { Prop } from "vue-property-decorator";
40-
import { getStore } from "../../store";
40+
import {
41+
commitSelectCharacter,
42+
commitUnselectCharacter,
43+
dispatchSaveState,
44+
readSelectedCharacters,
45+
} from "../../store";
4146
import {
4247
allCharacterMetadata,
4348
CharacterId,
@@ -66,21 +71,22 @@ export default class extends Vue {
6671
}
6772
6873
get selectedCharacterId() {
69-
return getStore().state.local.selectedCharacters[this.gameId];
74+
const selectedCharacters = readSelectedCharacters(this.$store);
75+
return selectedCharacters[this.gameId];
7076
}
7177
7278
public async onChange(
7379
characterId: CharacterId<GameId> | undefined,
7480
): Promise<void> {
7581
if (characterId !== undefined) {
76-
getStore().commit.selectCharacter({
82+
commitSelectCharacter(this.$store, {
7783
gameId: this.gameId,
7884
characterId,
7985
});
8086
} else {
81-
getStore().commit.unselectCharacter(this.gameId);
87+
commitUnselectCharacter(this.$store, this.gameId);
8288
}
83-
await getStore().dispatch.saveState();
89+
await dispatchSaveState(this.$store);
8490
}
8591
8692
public filter(

src/components/training/TrainingHeader.vue

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,10 @@
8181
</template>
8282

8383
<script lang="ts">
84-
import { getStore } from "@/store";
8584
import { allCharacterMetadata, GameId } from "@/tech/AllCharacterMetadata";
8685
import Vue from "vue";
8786
import Component from "vue-class-component";
87+
import { dispatchRestoreState, readSelectedCharacters } from "../../store";
8888
import CharacterSelector from "./CharacterSelector.vue";
8989
import TrainingPerformanceSelector from "./TrainingPerformanceSelector.vue";
9090
@@ -99,7 +99,8 @@ export default class extends Vue {
9999
public value: number | null | undefined = null;
100100
101101
get selectedCharacter() {
102-
return getStore().state.local.selectedCharacters[this.gameId];
102+
const selectedCharacters = readSelectedCharacters(this.$store);
103+
return selectedCharacters[this.gameId];
103104
}
104105
105106
get selectedCharacterName(): string {
@@ -112,7 +113,7 @@ export default class extends Vue {
112113
}
113114
114115
public async created() {
115-
await getStore().dispatch.restoreState();
116+
await dispatchRestoreState(this.$store);
116117
if (this.selectedCharacter === null) {
117118
this.value = 2;
118119
}

src/components/training/TrainingPanel.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@
4646
</template>
4747

4848
<script lang="ts">
49-
import { getStore } from "@/store";
5049
import allTechData, { getTechMetadata, TechId } from "@/tech/AllTechMetadata";
5150
import {
5251
AllTechVariants,

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Vue.config.productionTip = false;
88

99
new Vue({
1010
router,
11-
store: store.original,
11+
store,
1212
vuetify,
1313
render: h => h(App),
1414
}).$mount("#app");

src/store.ts

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {
55
Performance,
66
updateItem,
77
} from "@/tech/SpacedRepetition";
8-
import { createDirectStore } from "direct-vuex";
98
import Vue from "vue";
109
import Vuex, { ActionContext } from "vuex";
10+
import { getStoreAccessors } from "vuex-typescript";
1111
import {
1212
CharacterId,
1313
GameAndCharacterId,
@@ -32,7 +32,7 @@ export interface PracticeSet<T extends TechId> {
3232
timestamp: number;
3333
}
3434

35-
interface State {
35+
interface MainState {
3636
version: 1;
3737
local: {
3838
loaded: boolean;
@@ -44,7 +44,13 @@ interface State {
4444
};
4545
}
4646

47-
const defaultState: State = {
47+
interface RootState {
48+
modules: {
49+
main: MainState;
50+
};
51+
}
52+
53+
const defaultState: MainState = {
4854
version: 1,
4955
local: {
5056
loaded: false,
@@ -60,11 +66,24 @@ const defaultState: State = {
6066

6167
Vue.use(Vuex);
6268

63-
const { store } = createDirectStore({
69+
type MainContext = ActionContext<MainState, RootState>;
70+
71+
const mainStore = {
72+
namespaced: true,
6473
state: defaultState,
6574
getters: {
75+
selectedCharacters(
76+
state: MainState,
77+
): MainState["local"]["selectedCharacters"] {
78+
return state.local.selectedCharacters;
79+
},
80+
recordedPracticeSets(
81+
state: MainState,
82+
): MainState["remote"]["recordedPracticeSets"] {
83+
return state.remote.recordedPracticeSets;
84+
},
6685
spacedRepetitionItems(
67-
state: State,
86+
state: MainState,
6887
): Map<SerializedTechVariant, SpacedRepetitionItem> {
6988
const result = new Map<SerializedTechVariant, SpacedRepetitionItem>();
7089
for (const practiceSet of state.remote.recordedPracticeSets) {
@@ -86,7 +105,7 @@ const { store } = createDirectStore({
86105
},
87106
mutations: {
88107
selectCharacter<T extends GameId>(
89-
state: State,
108+
state: MainState,
90109
gameAndCharacterId: GameAndCharacterId<T>,
91110
): void {
92111
const { gameId, characterId } = gameAndCharacterId;
@@ -96,25 +115,25 @@ const { store } = createDirectStore({
96115
// with `characterId as CharacterId<typeof gameId>` doesn't work.
97116
selectedCharacters[gameId] = characterId;
98117
},
99-
unselectCharacter(state: State, gameId: GameId): void {
118+
unselectCharacter(state: MainState, gameId: GameId): void {
100119
state.local.selectedCharacters[gameId] = null;
101120
},
102121
recordPracticeSet<T extends TechId>(
103-
state: State,
122+
state: MainState,
104123
practiceSet: PracticeSet<T>,
105124
): void {
106125
state.remote.recordedPracticeSets.push(practiceSet);
107126
},
108-
restoreState(state: State, newState: State) {
127+
restoreState(state: MainState, newState: MainState) {
109128
Object.assign(state, newState);
110129
},
111130
},
112131
actions: {
113-
async saveState(context: ActionContext<State, {}>): Promise<void> {
132+
async saveState(context: MainContext): Promise<void> {
114133
const state = { ...context.state, loaded: false };
115134
localStorage.setItem("state", JSON.stringify(state));
116135
},
117-
async restoreState(context: ActionContext<State, {}>): Promise<void> {
136+
async restoreState(context: MainContext): Promise<void> {
118137
if (context.state.local.loaded) {
119138
return;
120139
}
@@ -123,7 +142,7 @@ const { store } = createDirectStore({
123142
if (previousStateString === null) {
124143
return;
125144
}
126-
const previousState: State = JSON.parse(previousStateString);
145+
const previousState: MainState = JSON.parse(previousStateString);
127146
const oldVersion: number = previousState.version;
128147
const currentVersion = context.state.version;
129148
if (oldVersion !== currentVersion) {
@@ -140,12 +159,41 @@ const { store } = createDirectStore({
140159
});
141160
},
142161
},
162+
};
163+
164+
export type MainStore = typeof mainStore;
165+
166+
const rootStore = new Vuex.Store({
167+
modules: {
168+
main: mainStore,
169+
},
143170
});
144171

145-
export default store;
172+
export type RootStore = typeof rootStore;
146173

147-
export type Store = typeof store;
174+
export default rootStore;
148175

149-
export function getStore(): Store {
150-
return store;
151-
}
176+
const { commit, read, dispatch } = getStoreAccessors<MainState, RootState>(
177+
"main",
178+
);
179+
180+
export const readSpacedRepetitionItems = read(
181+
mainStore.getters.spacedRepetitionItems,
182+
);
183+
export const readRecordedPracticeSets = read(
184+
mainStore.getters.recordedPracticeSets,
185+
);
186+
export const readSelectedCharacters = read(
187+
mainStore.getters.selectedCharacters,
188+
);
189+
export const dispatchRestoreState = dispatch(mainStore.actions.restoreState);
190+
export const dispatchSaveState = dispatch(mainStore.actions.saveState);
191+
export const commitRecordPracticeSet = commit(
192+
mainStore.mutations.recordPracticeSet,
193+
);
194+
export const commitSelectCharacter = commit(
195+
mainStore.mutations.selectCharacter,
196+
);
197+
export const commitUnselectCharacter = commit(
198+
mainStore.mutations.unselectCharacter,
199+
);

0 commit comments

Comments
 (0)