Skip to content

Instantly share code, notes, and snippets.

@soerenmartius
Last active June 29, 2023 02:28
Show Gist options
  • Save soerenmartius/ad62ad59b991c99983a4e495bf6acb04 to your computer and use it in GitHub Desktop.
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 )
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,
}
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,
}
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
@ux-engineer
Copy link

Just recently Evan You confirmed in Twitter that Pinia is Vuex 5.

@nelisbijl
Copy link

Okay, that was a useful tip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment