Planex
Super simple type-safe state-management stores for vue@2
with @vue/composition-api
.
planex
is a light wrapper around vue
's reactive
/computed
functions, providing some handy utility you don't get directly with just reactive
alone, such as:
- Reactive, cached getters by just using JavaScript getters/setters in your store definition.
- Create store from class definitions (recommended)
- Support for store extension via class inheritance
Define store
Store definitions are simple: any regular property is part of state, any getter is a getter, and any setter and method is an action. Each call to defineStore
creates a hook for a single store. Once the store is retrieved, it is cached so any other use of the hook produces the same store, so its values can be reactive across different components.
Class syntax
const useCounter = defineStore(
class {
// state
count = 0
// action
increment() {
this.count++
}
// getter
get next() {
return this.count + 1
}
}
)
Object syntax
const useCounter = defineStore({
// state
count: 0,
// action
increment() {
this.count++
},
// getter
get next() {
return this.count + 1
},
})
Function syntax
const useCounter = defineStore(() => ({
// state
count: 0,
// action
increment() {
this.count++
},
// getter
get next() {
return this.count + 1
},
}))
Use other stores
const useStore = defineStore(
class {
get currentCount() {
return useCounter().count
}
}
)
Classes are recommended
The class syntax has some benefits. For one, if you pass an object literal to defineStore
, TypeScript may interpret your getters as any
unless you manually annotate their return type.
Classes also make it far easier to extend (inherit) the definitions of other stores. You can either define the classes separately:
class MyStore {}
const useStore = defineStore(MyStore)
// elsewheere
class MyOtherStore extends MyStore {}
const useOtherStore = defineStore(MyOtherStore)
Or you can use the store hook's $class
property to extend it:
const useStore = defineStore(class {})
// elsewheere
const useOtherStore = defineStore(class extends useStore.$class {})
Note: classes must have a parameterless constructor. This is so defineStore
can instantiate the class and cache the result. Otherwise the store hook would have to access the constructor arguments, which would only be used the first time the store is inialized. If you want the store to be able to access some variable configuration, use defineStore
inside a factory function that captures the configuration:
const useStore = (api: () => Promise<string[]>) => {
return defineStore(
class {
items: string[] = []
fetchData() {
api().then(items => {
this.items = items
})
}
}
)() // call the hook at the end of your wrapper
}
Use your store
Use the result of the defineStore hook
<template>
<button type="button" @click="store.increment">{{ store.count }}</button>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api'
import { useCounter } from './counter'
export default defineComponent({
setup: () => {
const store = useCounter()
return { store }
},
})
</script>
Use $refs
The $refs
property of the use store hook returned from defineStore
contains the properties of the store as refs, (except for the actions, they're there as regular functions). This makes it easy to spread to the store into your setup data so you can access them directly by name instead of via store.*
.
<template>
<button type="button" @click="increment">{{ count }}</button>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api'
import { useCounter } from './counter'
export default defineComponent({
setup: () => {
return { ...useCounter.$refs }
},
})
</script>
Use with Options API
Use $mapComputed
and $mapMethods
methods of your use store hook to pass the properties to the respective options API config.
<template>
<button type="button" @click="increment">{{ count }}</button>
</template>
<script lang="ts">
import { defineComponent } from '@vue/composition-api'
import { useCounter } from './counter'
export default defineComponent({
computed: {
...useCounter.$mapComputed(),
},
methods: {
...useCounter.$mapMethods(),
},
})
</script>