1

First of all, I love Vue 3. I really enjoy it over many other frameworks. But I think I have found a limitation. I am trying to wrap some of my very complicated logic in class where internally each instance does the dirty work. This feels more natural for me. But when my instances are wrapped with reactive(), everything seems to break.

Here is an example:

export class Container {
  public stat: Ref<Stat> = ref({ cpu: 0 });
  private _throttledStatHistory: UseThrottledRefHistoryReturn<Stat, Stat>;

  constructor(
    public readonly id: string,
    // more fields not shown...
  ) {
    this._throttledStatHistory = useThrottledRefHistory(this._stat, { capacity: 300, deep: true, throttle: 1000 });
  }

  public statHistory: ComputedRef<UseRefHistoryRecord<Stat>[]> = computed(
    () => this._throttledStatHistory.history.value
  );
}

I can use this object with something like

const container = new Container("123")
container.stat.value = { cpu: 1}
container.stat.value = { cpu: 2}

However, when using reactive like so:

const myRef = reactive(new Container("123"))

Everything seems to break:

myRef.value.stat.value // stat is no longer a ref. It is now a proxy
myRef.value.stat.value = {cpu: 3} // also breaks as statHistory is not updated at all 

My assumption is that everything being wrapped in reactive breaks.

Somethings can be fixed with toRaw() like toRaw(myRef.value).stat.value = ... but that feels unnatural.

Note if I make stat private with private stat: Ref<Stat> = ref({ cpu: 0 }); then the problem still persists. I expected private members to not be affected by reactive.

I am going crazy debugging this. What is the proper way to do class with internal reactivity?

Thanks!

Amir Raminfar
  • 33,777
  • 7
  • 93
  • 123
  • See https://stackoverflow.com/questions/73163014/how-to-make-a-class-getter-reactive-in-vue-js-3-with-composition-api – Estus Flask Oct 03 '22 at 19:07
  • Also https://stackoverflow.com/questions/69050412/vue-composition-api-and-reactive-class . This idea is faulty by design. Vue reactivity wasn't designed to be used with classes, you won't benefit from using them instead of plain objects. `ref(new Container("123"))` - can be solved with shallowRef, but this is just a symptom of a bigger problem – Estus Flask Oct 03 '22 at 19:08
  • Thanks. I saw those already. The problem is a little different because I am seeing my refs being converted to regular proxies which is unexpected. – Amir Raminfar Oct 03 '22 at 19:33
  • I think you're right though. Vue wasn't designed for this use case. Which is unfortunate. I may have to figure out a way to move all the reactivity of `stat` to outside. – Amir Raminfar Oct 03 '22 at 19:34
  • Your problem can be seen here https://stackoverflow.com/a/73163509/3731501 . It's expected and documented, https://vuejs.org/guide/essentials/reactivity-fundamentals.html#ref-unwrapping-in-reactive-objects . You'll have less problems when rewriting Container to regular composable function. Nothing here really requires a class. – Estus Flask Oct 03 '22 at 19:47
  • So I was already using that solution with `computed`. Still didn't work. But reading the doc I found that it is documented for `reactive` to unwrap all refs. That's good to see. Then I saw `shallowRef` as you suggested and `markRaw`. I am going to play with those. – Amir Raminfar Oct 03 '22 at 20:25

1 Answers1

1

Thanks Estus for suggestions.

Looks like there are two options that worked for me:

  1. Use shallow reactive like
const myRef = shallowReactive(new Container("123"))

This works, but the con is that all my other code needs to be aware of using shallow.

  1. Use markRaw
export class Container {
  public stat: Ref<Stat>;
  private throttledStatHistory: UseThrottledRefHistoryReturn<Stat, Stat>;

  constructor(
    public readonly id: string,
    // skipped
  ) {
    this.stat = markRaw(ref({ cpu: 0, memory: 0, memoryUsage: 0 }));
    this.throttledStatHistory = markRaw(
      useThrottledRefHistory(this.stat, { capacity: 300, deep: true, throttle: 1000 })
    );
  }

  public getStatHistory() {
    return this.throttledStatHistory.history.value;
  }
}

I liked the second option because it doesn't depend on shallow being used properly.

Amir Raminfar
  • 33,777
  • 7
  • 93
  • 123