Skip to content

Instantly share code, notes, and snippets.

@bmingles
Created December 12, 2017 02:44
Show Gist options
  • Save bmingles/8dc0ddcb87aeb092beb5a12447b10a36 to your computer and use it in GitHub Desktop.
Save bmingles/8dc0ddcb87aeb092beb5a12447b10a36 to your computer and use it in GitHub Desktop.
Making Vuex stores type safe
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'
});
}
}
});
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);
/**
* 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