vuejs / docs

📄 Documentation for Vue 3
https://vuejs.org
Other
2.93k stars 4.44k forks source link

Document how some classes can avoid being reactivity #692

Open jods4 opened 3 years ago

jods4 commented 3 years ago

What problem does this feature solve?

Apologies if this is well documented somewhere, I did not find it.

It is common to use some "primitive" objects that actually represent simple value. Built-in the browser we have Promise, Date, RegExp and friends. In libraries I could cite Moment or Luxon (DateTime, Interval) that serve similar purposes.

Those objects shouldn't be wrapped by reactive Vue proxy.

Browser built-ins are already taken care of by Vue itself, but JS libraries can easily be wrapped when unsuspecting. There's a need for an official way to prevent some class of objects from being reactive.

What does the proposed API look like?

In Vue 3.0.2, I see two ways to achieve this.

Either add __v_skip: true on the prototype of those objects. This would require documenting __v_skip as an official public API with long-term stability and support.

Or trick Vue into thinking they are unknown object types by modifying their type tag, i.e. add Symbol.toStringTag on their prototype and make it anything but the string "Object".

Option 3: add a new way to achieve this.

I personally use the second option and I think it's good enough, but it would need to be documented officially.

LinusBorg commented 3 years ago

To be sure, is your issue here that people might forget to

  1. not use reacive() on such objects in the first place and
  2. use markRaw() on such an instance when adding it as property value on a reactive object?

A small code example of the pitfalls you wanna see improved would help.

jods4 commented 3 years ago

To illustrate my response, let's consider DateTime from Luxon. Suppose I use this as a replacement for the built-in Date.

  1. Is unavoidable, given the deep-by-default philosophy of Vue. Let's create a reactive arrays of some dates:
    let holidays = reactive(['2020-12-25', '2020-12-31'].map(DateTime.fromISO))
    holidays.push(DateTime.fromISO('2021-12-25'))

    All those dates will be made reactive.

Deep reactivity is very invasive. The argument here is the same as when we discussed how to mix proxies and non-proxies: it's just simpler and safer to turn everything in your state graph into a proxy (or mark it raw at creation if you want to avoid the cost of a proxy) -- which segue nicely into...

  1. markRaw is hardly an option with these kinds of objects. There are typically lots of them, in many places. It's just neither efficient nor practical. Imagine marking every Bluebird promise markRaw? Worse: some of those "value-like" object are designed as immutable object, e.g. Luxon. When you do date arithmetic, you get new instances:
    // There's 3 different DateTime instances on next line
    const eomNextYear = DateTime.now().endOf('month').add({ year: 1 })

Vue proxifies objects based on a white-list. It takes steps to avoid turning built-in objects such as Date or Promise into reactive proxies. Similar objects exist in JS libraries and the motivation is the same.

jods4 commented 3 years ago

To add a little bit of context: how did I stumble upon this?

Like everyone else I'm sure, I didn't give this a thought beforehand. My dates got proxified inside some reactive graph, I wasn't aware. I noticed it because it created some changes in behavior, which turned into bugs.