davestewart/vue-class-store

Nested getters as computed

Closed this issue · 3 comments

Hello, I've recently run into an edge case, where I'd like to use es6 class getter as a computed property.
Yet the issue is that this getter is nested in my app's store.
i.e.

class Store {
  child = new Child()

  bar = 1

  // gets converted to computed
  get foo () {
    return this.bar
  }
}

class Child {
  bar = 2
  // does not get converted to computed
  get foo () {
    return this.bar
  }
}

The getter -> computed property conversion works so long as this descriptor lives on the root of the store/the class/object passed to VueStore.create().

It seems this could be possible to correct by messing around with

function getDescriptors (model: R) {

But perhaps the better question is if this a recommended practice?
Would it be better to not nest classes in a root store but rather provide/inject multiple different reactive stores?
If allowing for nested computed values (which would open up the possibility of nested watchers), then I could possibly even get a PR in the next few weeks. I'd just like to know if this was limited intentionally for performative/overhead concerns before tackling it.
Thanks!

Hey @Mootook,

Thanks for the question!

There are several approaches you could take:

  1. make the child a @VueStore too
  2. use provide / inject, as you say
  3. add a root $store object to the Vue prototype with all the child stores you need

For this last option, you can use TypeScript prototype augmentation to gain type completion throughout your app (I will have to publish this in the docs at some point!)

Create a root store, here in app/store/index.ts:

import Vue from 'vue'

// root store class which can hold child stores
class RootStore {}

// create the store instance
export const store = new RootStore()

// attach to the Vue prototype
Vue.prototype.$store = store

// tell vue about the built-in store
declare module 'vue/types/vue' {
  interface Vue {
    $store: RootStore
  }
}

In any store file:

import $store from '@/app/store'

// local store
@VueStore
class FooStore {
  blah () {
    // blah
  }
}

// attach the local store to the global store
$store.foo = new FooStore()

// update the global store reference
declare module '@/app/store' {
  interface RootStore {
    foo: FooStore,
  }
}

Then you can both import the stores explicitly, or access them in components:

this.$store.foo.blah() // should get full autocomplete

My setup is a bit more complex than this, so not sure if this example code will run straight-off-the-bat, but this should get you started / going in the right direction.

Re, nested watchers, you can do this already (as VCS is just Vue):

@VueStore
class SomeStore {
  child: SomeChild

  'on:child.sub.prop' (value, oldValue) {
    // do something
  }
}

I think someone raised an issue of it not working in a specific case, but I can't recall right now the specifics.

Try it and see?

@davestewart Thanks for the prompt reply as always!

Option 1 makes the most sense in my use case. I wasn't sure if there was some guideline about nested reactive objects, but I'll give it a look today.

Also, not sure if this has been considered, but it could be helpful to abstract some of index.ts out and make some of the conversion public utilities, something like gettersToComputed(). Not essential, just a thought

Thanks for the prompt reply as always!

No problem! I'm stoked that you're finding it useful :)

Also, not sure if this has been considered, but it could be helpful to abstract some of index.ts out and make some of the conversion public utilities, something like gettersToComputed()

Hmm. Not sure I understand, as the lib is designed to convert an entire class.

Can you expand on your thinking / use case?