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!

6
  • See stackoverflow.com/questions/73163014/… Commented Oct 3, 2022 at 19:07
  • 1
    Also stackoverflow.com/questions/69050412/… . 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 Commented Oct 3, 2022 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. Commented Oct 3, 2022 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. Commented Oct 3, 2022 at 19:34
  • Your problem can be seen here stackoverflow.com/a/73163509/3731501 . It's expected and documented, vuejs.org/guide/essentials/… . You'll have less problems when rewriting Container to regular composable function. Nothing here really requires a class. Commented Oct 3, 2022 at 19:47

1 Answer 1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.