vuex-accessors
v3.0.6
Published
Type-safe wrapper for vuex.
Downloads
17
Readme
vuex-accessors
This library is a simple wrapper for vuex API. Its purpose is to provide better type safety when using vuex with typescript.
Installation
npm install --save vuex-accessors
Example
Let's say we want to create a vuex store which will manage the state of a single numeric variable counter
. We start by providing an interface and implementation for its state:
./root-state.ts
export interface IRootState {
counter: number
}
export const rootState: IRootState = {
counter: 0
}
Next we define accessors for its getters, mutations and actions in a separate file.
./root.ts
import {createRootOptions, AccessorFactory} from "vuex-accessors"
import {rootState, IRootState} from "./root-state"
// first we instantiate a factory instance which we will use to create accessors.
const f = AccessorFactory.root<IRootState>()
// now we create an object with accessors
export const root = {
getters: {
counter: f.getter<number>(state => state.counter)
},
mutations: {
setCounter: f.mutationWPayload<number>((state, payload) => state.counter = payload),
incrementCounter: f.mutation(state => state.counter++),
decrementCounter: f.mutation(state => state.counter--)
},
actions: {
countdown: f.action<number>(context => {
return new Promise<number>(resolve => {
const interval = setInterval(() => {
// here we are alraeady accessing getter through our accessor with type safety
// `true` flag will be explained later
const currentValue = counter.getters.counter.get(context.getters, true)
log.debug(currentValue)
if (currentValue === 0) {
clearInterval(interval)
resolve(currentValue)
} else {
counter.mutations.decrementCounter.commit(context)
}
}, 1000)
})
})
}
}
// Now we can use our `root` object with accessors to generate the actual StoreOptions<IRootState> object for
// vuex API. `createRootOptions` function expects an object which conforms to the
// IAccessorsBundle<IRootStore, any> interface so type checking is performed here.
export const storeOptions = createRootOptions(rootState, root)
We exported two objects from root.ts: root
and storeOptions
. storeOptions
is only required for store
initialization, but root
is later used for access. storeOptions
now looks like this.
{
getters: {
counter: state => state.counter
},
mutations: {
setCounter: (state, payload) => state.counter = payload,
incrementCounter: state => state.counter++,
decrementCounter: state => state.counter--
},
actions: {
countdown: context => {
return new Promise<number>(resolve => {
const interval = setInterval(() => {
const currentValue = root.getters.counter.get(context.getters, true)
log.debug(currentValue)
if (currentValue === 0) {
clearInterval(interval)
resolve(currentValue)
} else {
counter.mutations.decrementCounter.commit(context)
}
}, 1000)
})
}
}
}
This is the StoreOptions<IRootState>
object which can now be used to initialize vuex store.
./store.ts
import Vue from "vue"
import Vuex, {Store} from "vuex"
import {IRootState} from "./root-state"
import {storeOptions} from "./root"
Vue.use(Vuex)
export const store: Store<IRootState> = new Vuex.Store<IRootState>(storeOptions)
Store can be safely accessed with accessors we defined in root
object.
import {root} from "... wherever root.ts is located relatively to this file ..."
// ... somewhere inside vue component
const store = this.$store
let currentCounterValue = root.getters.counter.get(store.getters)
log.debug(currentCounterValue) // 0
root.mutations.setCounter.commit(store, 2)
currentCounterValue = root.getters.counter.get(store.getters)
log.debug(currentCounterValue) // 2
root.mutations.incrementCounter.commit(store)
currentCounterValue = root.getters.counter.get(store.getters)
log.debug(currentCounterValue) // 3
root.actions.countdown.dispatch(store).then(value => {
log.debug(value) // 0
})
As you can see, getters, mutations, and actions are used by calling get
, commit
and dispatch
methods respectively on their accessor objects. commit
and dispatch
must be provided with context
or store
object. They will internally call vuex's commit
or dispatch
function with full namespace and mutation/action name. get
on the other hand receives getters
object and an optional second parameter local
. local
is false by default. If you set it to true, getter will be called only by name, not the full path (namespace + "/" + name). You can set this flag when you call getter of a namespaced module inside its own context (see action accessor in the example above).
Modules
Modules are created in a similar fashion. There are two differences:
AccessorFactory
is instantiated by callingmodule(namespace?: string)
static method and optionally providing namespace if module should be namespaced.- Instead of
createRootOptions
,createModule
is called to generateModule
object for registration in vuex store.
API
Here is the summary of all factory methods available for the creation of accessors.
/**
* Create getter accessor.
*/
getter<ReturnT>(
func: (state: StateT, getters: any, rootState?: RootStateT, rootGetters?: any) => ReturnT,
name?: string
)
/**
* Create mutation accessor.
*/
mutation(
func: (state: StateT) => void,
name?: string
)
/**
* Create mutation accessor with payload.
*/
mutationWPayload<PayloadT>(
func: (state: StateT, payload: PayloadT) => void,
name?: string
)
/**
* Create action accessor.
*/
action<ReturnT>(
func: (context: ActionContext<StateT, RootStateT>) => Promise<ReturnT>,
name?: string
)
/**
* Create action accessor with payload.
*/
actionWPayload<PayloadT, ReturnT>(
func: (context: ActionContext<StateT, RootStateT>, payload: PayloadT) => Promise<ReturnT>,
name?: string
)
Each factory method receives optional second parameter name
. We didn't use this parameter in our example.
If provided, name
will be the actual key used in the generated StoreOptions
or Module
object. If you don't provide it,
accessor's key will be used implicitly. For example, if we had defined our counter
getter like this:
// ...
getters: {
counter: f.getter<number>(state => state.counter),
"getCounter"
}
// ...
the actual StoreOptions
object would then look like this:
// ...
getters: {
getCounter: state => state.counter
}
// ...