Global store discussion
davestewart opened this issue · 4 comments
It may be too early to declare victory, but I appear to have gotten a nice TypeScript workflow going using:
- a
RootStore
class instance - a global
this.$store
variable - Module Augmentation which provides autocomplete throughout the project
Note: I'm setting this up in an external project, so some of the imports may look a little funky
The root store class looks like this:
export class RootStore {
public $add (name: string, store: any): void {
if (name in this) {
throw new Error(`Unable to add local store "${name}" as property already exists`)
}
this[name] = store
}
public $remove (name: string): void {
if ((name in this) && !name.startsWith('$')) {
delete this[name]
}
}
constructor (modules: Record<string, any> = {}) {
Object.keys(modules).forEach(key => {
this.$add(key, modules[key])
})
}
}
Install looks like this:
export function install (Vue: VueConstructor, modules: Record<string, any> = {}) {
Vue.prototype.$store = new RootStore(modules)
}
My project's store/index.ts
looks like this:
import Vue from 'vue'
import { RootStore, install } from '@/packages/store'
// local modules
import app, { AppStore } from './app'
import ui, { UiStore } from './ui'
// tell typescript about modules
declare module '@/packages/store/root' {
interface RootStore {
app: AppStore,
ui: UiStore,
}
}
// tell vue about the built-in store
declare module 'vue/types/vue' {
interface Vue {
$store: RootStore
}
}
// install and add modules
Vue.use(install, {
app,
ui,
})
// ensure other modules importing store directly know what it is
export default Vue.prototype.$store as RootStore
You can add modules individually using a combination of instantiation and augmentation:
// import root store
import store from '@/store'
// augment root store with the new property
declare module '@/packages/store/root' {
interface RootStore {
tools: ToolsStore,
}
}
// local vue class store setup
@VueStore
class ToolsStore {
resize () {
bus.call('tools/windows/resize')
}
}
// physically add the local store to the root store
store.$add('tools', new ToolsStore())
Autocomplete works great in .vue
code and templates:
I know some of the TS boilerplate looks a little funky, but:
- it allows a root store to be created without explicitly declaring properties
- it allows local stores to be added independently of the root store declarations
Happy to hear any thoughts!
@davestewart -- Took me a bit as I'm not as familiar with TypeScript, but I think I get the gist. Looks nice. Would this replace the entire provide/inject usage of the current release?
setup () {
const { tools } = useStore()
return { tool }
// vs.
return { tools: this.$store.tools }
}
Globally attaching the store would be great and eliminate some boilerplate at the component level. Also, just a note, Vue is no longer exposed in the vue3 package. It would instead have to be attached to the app instance...something like:
app.config.globalProperties.$store = new RootStore(modules) // or something
Hmmm. We have inverse knowledge-sets!
I can only really comment regarding Vue 2 right now, but I'm finding that this.$store.model
just feels a little "more global" than this.$model
, but you can do both, of course.
Vue 3 wise, I don't suppose it makes any difference, but you would have more choice.
Seeing as Vue 3's setup function does make it easier to type variables, perhaps it's not even needed in Vue 3?
I agree with this.$store.anything
feeling more global. And it does steer clear of the risk of assigning some variable to a nested property or destructuring an object and losing reactivity. I know even vue3 has some guidelines on that especially regarding props in the composition API.
Though, I have found a nice workflow with the current vue-class-store partnered with a jsconfig and jsdoc, which allows for typing "guides" but not necessarily strict type checking like what TS offers.
I think the way you posted works really well and keeps in tune with how a lot of things are packaged into Vue2 apps. So perhaps the best idea is just to press on with that. Vue3 might have to be its own discussion.