Created
December 12, 2017 02:44
-
-
Save bmingles/8dc0ddcb87aeb092beb5a12447b10a36 to your computer and use it in GitHub Desktop.
Making Vuex stores type safe
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 { Dispatcher } from './store'; | |
export default Vue.component('some-component', { | |
template, | |
created() { | |
this.fetchData(); | |
}, | |
methods: { | |
/** | |
* Fetch our data. | |
* Using this type annotation with our Dispatcher type | |
* to make dispatching type safe (including the 'loadFoo' value) | |
*/ | |
fetchData(this: { $store: Dispatcher }) { | |
this.$store.dispatch('loadFoo', { | |
id: 'some-id' | |
}); | |
} | |
} | |
}); |
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 Vue from 'vue'; | |
import Vuex, { MutationPayload } from 'vuex'; | |
import axios from 'axios'; | |
import { | |
ActionDictionary, | |
MutationDictionary, | |
StoreOptions | |
} from './vuex-type-ext'; | |
/* | |
* This gist shows a way to provide type safety for vuex stores. | |
*/ | |
/* First define types for state, mutation payloads, and action payloads */ | |
/** Define an interface for our state. */ | |
interface IState { | |
foo: string; | |
bar: number; | |
} | |
/** Map of mutation types to payload types. */ | |
type MutationPayloadMap = { | |
'setFoo': { | |
data: string; | |
}, | |
'setBar': { | |
data: number; | |
} | |
}; | |
/** Map of action types to payload types. */ | |
type ActionPayloadMap = { | |
'loadFoo': { | |
id: string; | |
}, | |
'loadBar': { | |
id: number; | |
} | |
}; | |
/** Alias to our Dispatcher type to save us some typing in our components */ | |
export type Dispatcher = DispatcherT<ActionPayloadMap>; | |
/* | |
* Define our storeOptions using the state, mutation payload, and action payload types. | |
* This is where the real magic happens. All methods will be type safe (including their string args), | |
* and the compiler will make sure you define all of them that were declared in the maps above. | |
*/ | |
const storeOptions: StoreOptions<IState, MutationPayloadMap, ActionPayloadMap> = { | |
state: { | |
foo: 'init', | |
bar: 0 | |
}, | |
mutations: { | |
/** Set our foo field */ | |
setFoo( | |
state: IState, | |
payload: { | |
data: string | |
} | |
) { | |
state.foo = payload.data; | |
}, | |
/** Set our bar field. */ | |
setBar( | |
state: IState, | |
payload: { | |
data: number | |
} | |
) { | |
state.bar = payload.data; | |
} | |
}, | |
actions: { | |
/** Load foo from backend. */ | |
loadFoo(store, payload) { | |
return axios.get<string>('/api/foo') | |
.then(({ data }) => { | |
// 'setFoo' and its payload are type safe | |
store.commit('setFoo', { | |
data | |
}); | |
}); | |
}, | |
/** Load bar from backend. */ | |
loadBar(store, payload) { | |
return axios.get<number>('/api/bar') | |
.then(({ data }) => { | |
// 'setBar' and its payload are type safe | |
store.commit('setBar', { | |
data | |
}); | |
}); | |
} | |
} | |
}; | |
/** Create our store */ | |
export const store = new Vuex.Store<IState>(storeOptions); |
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
/** | |
* Types for helping make commit and dispatch methods in vuex stores type safe. | |
*/ | |
/** | |
* Type defining the commit method portion of our store. | |
*/ | |
export type Mutator<TMutationPayloadMap> = { | |
commit: <T extends keyof TMutationPayloadMap>( | |
mutationType: T, | |
payload: TMutationPayloadMap[T] | |
) => void; | |
}; | |
/** | |
* Type defining the dispatch method portion of our store. | |
*/ | |
export type Dispatcher<TActionPayloadMap> = { | |
dispatch: <T extends keyof TActionPayloadMap>( | |
actionType: T, | |
payload: TActionPayloadMap[T] | |
) => Promise<any>; | |
}; | |
/** | |
* Type for mapping action type strings to action payloads. | |
*/ | |
export type ActionDictionary<TActionPayloadMap, TMutationPayloadMap> = { | |
[P in keyof TActionPayloadMap]: ( | |
store: Mutator<TMutationPayloadMap>, | |
payload: TActionPayloadMap[P] | |
) => void; | |
}; | |
/** | |
* Type for mapping mutation type strings to mutation payloads. | |
*/ | |
export type MutationDictionary<TState, TMutationPayloadMap> = { | |
[P in keyof TMutationPayloadMap]: (state: TState, payload: TMutationPayloadMap[P]) => void; | |
}; | |
/** | |
* Type for options passed to new Vuex.Store() | |
*/ | |
export type StoreOptions<TState, TMutationPayloadMap, TActionPayloadMap> = { | |
state: TState, | |
mutations: MutationDictionary<TState, TMutationPayloadMap>, | |
actions: ActionDictionary<TActionPayloadMap, TMutationPayloadMap> | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment