Last active
June 29, 2023 02:28
-
-
Save soerenmartius/ad62ad59b991c99983a4e495bf6acb04 to your computer and use it in GitHub Desktop.
Vue 3 with Typescriptt and Vuex 4 Typed Modules Examples ( with real types )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
ActionContext, | |
ActionTree, | |
GetterTree, | |
MutationTree, | |
Module, | |
Store as VuexStore, | |
CommitOptions, | |
DispatchOptions, | |
} from 'vuex' | |
import { State as RootState } from '@/store' | |
// Declare state | |
export type State = { | |
isAuthenticated: boolean | |
} | |
// Create initial state | |
const state: State = { | |
isAuthenticated: false, | |
} | |
// mutations enums | |
export enum MutationTypes { | |
SET_USER_AUTHENTICATED = 'SET_USER_AUTHENTICATED', | |
} | |
// Mutation contracts | |
export type Mutations<S = State> = { | |
[MutationTypes.SET_USER_AUTHENTICATED](state: S): void | |
} | |
// Define mutations | |
const mutations: MutationTree<State> & Mutations = { | |
[MutationTypes.SET_USER_AUTHENTICATED](state: State) { | |
state.isAuthenticated = true | |
}, | |
} | |
// Action enums | |
export enum ActionTypes { | |
SIGNIN = 'SIGNIN', | |
} | |
// Actions context | |
type AugmentedActionContext = { | |
commit<K extends keyof Mutations>( | |
key: K, | |
payload: Parameters<Mutations[K]>[1], | |
): ReturnType<Mutations[K]> | |
} & Omit<ActionContext<State, RootState>, 'commit'> | |
// Actions contracts | |
export interface Actions { | |
[ActionTypes.SIGNIN]( | |
{ commit }: AugmentedActionContext, | |
payload: { username: string; password: string }, | |
): void | |
} | |
// Define actions | |
export const actions: ActionTree<State, RootState> & Actions = { | |
async [ActionTypes.SIGNIN]( | |
{ commit }, | |
payload: { username: string; password: string }, | |
) { | |
try { | |
// some logic that logs a user in | |
} catch (err) { | |
// some error handling logic | |
} | |
}, | |
} | |
// getters types | |
export type Getters = { | |
isAuthenticated(state: State): boolean | |
} | |
// getters | |
export const getters: GetterTree<State, RootState> & Getters = { | |
isAuthenticated: (state) => { | |
return state.isAuthenticated | |
}, | |
} | |
//setup store type | |
export type Store<S = State> = Omit< | |
VuexStore<S>, | |
'commit' | 'getters' | 'dispatch' | |
> & { | |
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>( | |
key: K, | |
payload: P, | |
options?: CommitOptions, | |
): ReturnType<Mutations[K]> | |
} & { | |
getters: { | |
[K in keyof Getters]: ReturnType<Getters[K]> | |
} | |
} & { | |
dispatch<K extends keyof Actions>( | |
key: K, | |
payload: Parameters<Actions[K]>[1], | |
options?: DispatchOptions, | |
): ReturnType<Actions[K]> | |
} | |
export const AuthModule: Module<State, RootState> = { | |
state, | |
mutations, | |
actions, | |
getters, | |
// Namespacing Vuex modules is tricky and hard to type check with typescript. | |
// Instead of namespacing, we could create our own namespacing mechanism by | |
// prefixing the value of the TypeScript enum with the namespace, e.g. | |
// enum TodoActions { | |
// AddTodo = 'TODO__ADD_TODO' | |
// } | |
// namespaced: true, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
ActionContext, | |
ActionTree, | |
GetterTree, | |
Store as VuexStore, | |
CommitOptions, | |
DispatchOptions, | |
MutationTree, | |
Module, | |
} from 'vuex' | |
import { State as RootState } from '@/store' | |
import DomainService, { ResponseDomain as Domain } from '@/services/domain' | |
import RedirectionService, { | |
ResponseRedirection as Redirection, | |
} from '@/services/redirection' | |
// Declare state | |
export type State = { | |
domains: Domain[] | |
redirections: Redirection[] | |
} | |
// Create initial state | |
const state: State = { | |
domains: [], | |
redirections: [], | |
} | |
// Mutations enums | |
export enum MutationTypes { | |
SET_DOMAINS = 'SET_DOMAINS', | |
SET_REDIRECTIONS = 'SET_REDIRECTIONS', | |
} | |
// Mutation contracts | |
export type Mutations<S = State> = { | |
[MutationTypes.SET_DOMAINS](state: S, domains: Domain[]): void | |
[MutationTypes.SET_REDIRECTIONS](state: S, redirections: Redirection[]): void | |
} | |
// Define mutations | |
const mutations: MutationTree<State> & Mutations = { | |
[MutationTypes.SET_DOMAINS](state: State, domains: Domain[]) { | |
state.domains = domains | |
}, | |
[MutationTypes.SET_REDIRECTIONS](state: State, redirections: Redirection[]) { | |
state.redirections = redirections | |
}, | |
} | |
// Action enums | |
export enum ActionTypes { | |
FETCH_DOMAINS = 'FETCH_DOMAINS', | |
FETCH_REDIRECTIONS = 'FETCH_REDIRECTIONS', | |
} | |
// Actions context | |
type AugmentedActionContext = { | |
commit<K extends keyof Mutations>( | |
key: K, | |
payload: Parameters<Mutations[K]>[1], | |
): ReturnType<Mutations[K]> | |
} & Omit<ActionContext<State, RootState>, 'commit'> | |
// Actions contracts | |
export interface Actions { | |
[ActionTypes.FETCH_DOMAINS]( | |
{ commit }: AugmentedActionContext, | |
teamId: string, | |
): void | |
[ActionTypes.FETCH_REDIRECTIONS]( | |
{ commit }: AugmentedActionContext, | |
payload: { teamId: string; domainName: string }, | |
): void | |
} | |
// Define actions | |
export const actions: ActionTree<State, RootState> & Actions = { | |
async [ActionTypes.FETCH_DOMAINS]({ commit }, teamId: string) { | |
// As this is just an example, a Service Implementation is out of scope. | |
// A service in my case is basically an wrapper for a certain API. | |
const domainService = new DomainService() | |
const domains = await domainService.getDomains(teamId) | |
commit(MutationTypes.SET_DOMAINS, domains) | |
}, | |
async [ActionTypes.FETCH_REDIRECTIONS]( | |
{ commit }, | |
payload: { teamId: string; domainName: string }, | |
) { | |
const redirectionService = new RedirectionService() | |
const { teamId, domainName } = payload | |
let redirections: Redirection[] = [] | |
redirections = redirections.concat( | |
await redirectionService.getRedirections(teamId, domainName), | |
) | |
commit(MutationTypes.SET_REDIRECTIONS, redirections) | |
}, | |
} | |
// Getters types | |
export type Getters = { | |
getDomains(state: State): Domain[] | |
getRedirections(state: State): Redirection[] | |
} | |
// Getters | |
export const getters: GetterTree<State, RootState> & Getters = { | |
getDomains: (state) => { | |
return state.domains | |
}, | |
getRedirections: (state) => { | |
return state.redirections | |
}, | |
} | |
// Setup store type | |
export type Store<S = State> = Omit< | |
VuexStore<S>, | |
'commit' | 'getters' | 'dispatch' | |
> & { | |
commit<K extends keyof Mutations, P extends Parameters<Mutations[K]>[1]>( | |
key: K, | |
payload: P, | |
options?: CommitOptions, | |
): ReturnType<Mutations[K]> | |
} & { | |
getters: { | |
[K in keyof Getters]: ReturnType<Getters[K]> | |
} | |
} & { | |
dispatch<K extends keyof Actions>( | |
key: K, | |
payload: Parameters<Actions[K]>[1], | |
options?: DispatchOptions, | |
): ReturnType<Actions[K]> | |
} | |
export const DomainModule: Module<State, RootState> = { | |
state, | |
mutations, | |
actions, | |
getters, | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createStore, createLogger } from 'vuex' | |
import { | |
AuthModule, | |
Store as AuthStore, | |
State as AuthState, | |
} from '@/modules/auth/store' | |
import { | |
DomainModule, | |
Store as DomainStore, | |
State as DomainState, | |
} from '@/modules/domain/store' | |
export type State = { | |
auth: AuthState | |
domain: DomainState | |
} | |
export type Store = AuthStore<Pick<State, 'auth'>> & | |
DomainStore<Pick<State, 'domain'>> | |
export const store = createStore({ | |
plugins: | |
process.env.NODE_ENV === 'production' | |
? [] | |
: [createLogger()], | |
modules: { AuthModule, DomainModule }, | |
}) | |
export function useStore(): Store { | |
return store as Store | |
} | |
export default store |
Good to see that even the author has abandoned his 'solution'
Where Vue 3 is all about Typescript, Vuex 4 definitely missed that. Their (almost) only argument for using Vuex is it's great logging. That's mainly needed because of the poor implementation of their store depending on name string references.
Not sure what Vuex 5 will bring but they have to hurry or everyone will have found/created an alternative.
Just recently Evan You confirmed in Twitter that Pinia is Vuex 5.
Okay, that was a useful tip
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
this looks extremely complicated. 🙄