vuejs3 - Hur man använder TypeScript + Composition API + Vuex (namnrymder)?
Jag försöker för närvarande skapa en Vue 3-applikation som använder Composition API med Vuex 4 (namespaced) skriven i TypeScript.
Det finns massor av olika tillvägagångssätt men inget av dem ser särskilt användbart ut just nu. Jag vill inte använda någon klassstilssyntax eller onödiga externa omslag för detta.
Här är ett minimalt exempel för att visa dig mina problem:
// package.json
"vue": "^3.0.0",
"vuex": "^4.0.2"
// store.ts
import {Store as VuexStore, createStore, Module, ActionTree} from "vuex";
export enum ModuleActionTypes {
Test = 'TEST'
}
interface ModuleActions {
[ModuleActionTypes.Test]():void
}
const actions: ActionTree<any, any> & ModuleActions = {
[ModuleActionTypes.Test]() : void {
console.log("test action dispatched")
}
}
type ModuleStore = Omit<VuexStore<any>, 'dispatch'>
& {
dispatch<K extends keyof ModuleActions>(
key: K
): ReturnType<ModuleActions[K]>
}
const module: Module<any, any> = {
namespaced: true,
actions: actions
}
type Store = ModuleStore
const store = createStore<Store>({
modules: {
module
}
})
export function useStore(): Store {
return store
}
Den intressanta eller knepiga delen är anropet från ett app-perspektiv. Jag har försökt flera olika tillvägagångssätt med olika resultat.
Metod 1 (misslyckad): Inbyggda createNamespacedHelpers() med mapActions eller useActions från vuex-paketet fungerar inte eftersom det kräver att this.$store är inställt, vilket inte är fallet med Vue 3 (Composition API) och Vuex 4.
GitHub-begäran om användning*
Metod 2 (misslyckades): Att använda vuex-composition-helpers kräver beroendet till @vue/composition-api vilket orsakar ett beroendehelvete när det inkluderas i Vue 3:s beroendegraf.
Metod 3 (misslyckades): Att flytta närmare koden: att använda den typade ModuleActionTypes för att skicka en åtgärd resulterar i ett [vuex] unknown action type: TEST-fel eftersom useStore() returnerar det allmänna lagret istället för delmodulen. Att ta bort namnrymden från modulen skulle uppenbarligen få det att fungera.
...
// App.vue
<script lang="ts">
import {defineComponent} from "vue";
import {ModuleActionTypes, useStore} from "./store";
export default defineComponent({
name: 'App',
setup() {
const store = useStore()
store.dispatch(ModuleActionTypes.Test)
}
})
</script>
...
Metod 4 (fungerande): "av-typ" modulens butik och använd en vanlig sträng för att skicka åtgärden.
// store.ts
...
type ModuleStore = VuexStore<any>
...
// App.vue
...
setup() {
const store = useStore()
store.dispatch(`module/${ModuleActionTypes.Test}`)
}
...
Dessutom kan interna och externa handlingstyper användas @see 47646176 för att undvika strängsammanfogning inom Vue-komponenten.
Finns det några idéer eller förslag på var lagret av delmodulen fortfarande kan skrivas och namnrymder är på plats?
Svar #1
Jag stötte på detta problem idag och kom på ett sätt hur detta kunde hanteras.
Problemet är främst att när man använder useStore()-metoden med Action-, Mutation- eller GetterTyper lägger den inte automatiskt till ett namnrymd. Jag valde en lösning som använder generiska typer och omsluter butiken i en funktion som lägger till namnrymden för getters, dispatch, commit och state-funktionaliteten.
// store/index.ts
export const rootStore = createStore({
modules: {
app: appModule,
}
});
export const rootStoreKey: InjectionKey<Store<RootState>> = Symbol()
function rootStoreToNamespacedStore<ActionTypes, Actions extends { [key: string]: any }, MutationsTypes, Mutations extends { [key: string]: any }, GetterTypes, Getters extends { [key: string]: any }, StateType>(namespace: string, store: Store<any>) {
return {
getters<K extends keyof Getters>(getterType: GetterTypes): ReturnType<Getters[K]> {
return store.getters[`${namespace}/${getterType}`];
},
dispatch<K extends keyof Actions>(payloadWithType: ActionTypes, payload: Parameters<Actions[K]>[1], options?: DispatchOptions): ReturnType<Actions[K]> {
return store.dispatch(`${namespace}/${payloadWithType}`, payload, options) as ReturnType<Actions[K]>;
},
commit<K extends keyof Mutations>(payloadWithType: MutationsTypes, payload: Parameters<Mutations[K]>[1], options?: CommitOptions): void {
return store.commit(`${namespace}/${payloadWithType}`, payload, options)
},
state: store.state[namespace] as StateType
};
}
export function useAppStore() {
const store = useStore(rootStoreKey);
return rootStoreToNamespacedStore<AppActionTypes, AppActions, AppMutationTypes, AppMutations, AppGetterTypes, AppGetters, IAppState>('app', store);
}
Detta kommer att låta dig använda det på samma sätt som du är van vid.
// some/component.vue
...
setup(props, { emit }) {
const appStore= useAppStore();
const count = computed(() => appStore.getters(AppGetterTypes.GET_COUNT));
// or access via the state
// const count = computed(() => appStore.state.count);
// dispatching using your defined types
appStore.dispatch(AppActionTypes.SET_COUNT, count+1);
return {
count,
};
},
...
Om du vill ha ett mer komplett exempel, ta en titt på hur jag implementerade det i mitt Github-modulerexempel
