Subscribe on changes!

Properties of objects that inherit from EventTarget are not reactive

avatar
Aug 24th 2023

Vue version

3.3.4

Link to minimal reproduction

https://codepen.io/leaverou/pen/WNLQQJa?editors=1110

Steps to reproduce

  1. Visit testcase
  2. Press "Increase foo.x". Notice how the value updates.
  3. Press "Increase bar.x". The value does not update

Also note in the console that app.foo is wrapped by a Proxy whereas app.bar is not.

What is expected?

bar should behave identically to foo

What is actually happening?

bar.x is not reactive.

System Info

No response

Any additional comments?

I wonder if this is intentional behavior to avoid wrapping DOM nodes and other built-ins. In that case, checking for its children explicitly might be a better choice, since extending EventTarget is the Web Platform's currently recommended way for authors to create data objects that can emit and listen to events. Or, perhaps there is a clever test to distinguish built-in EventTarget descendants from author objects. Anyhow, it would be unfortunate if supporting events in one's objects breaks Vue — especially since this seems like a small improvement I can easily see people doing thinking it cannot break anything (e.g. see articles like this)

avatar
Aug 26th 2023

Vue only allow ordinary objects and a few built-ins (Array, Map, etc.) to be made reactive (source).

The crux is how Vue currently checks if an object x is ordinary: it checks if x.toString() equals "[object Object]" (source).

While foo.toString() returns "[object Object]", bar.toString() returns "[object EventTarget]". I've checked that overriding the Symbol.toString getter in the class to return "Object", although hacky-ish, indeed fix the reactivity problem (at least on the provided demo).

avatar
Aug 26th 2023

Thanks, that's a great workaround! Hacky, yes, and fragile, but could work for now. Leaving this open in hopes for a less hacky solution down the line.