optimize array tracking (fix #4318) by jods4 · Pull Request #9511 · vuejs/core (original) (raw)

This PR implements the optimisations proposed in #4318. Shortly:

Unrelated changes included in this PR

New public APIs

@vue/reactivity exposes readArray(source, deep = false).

This is an advanced reactivity function that returns the underlying raw array and tracks it entirely.
As this is a performance oriented API, an extra parameter indicates if deep reactive arrays return proxified array items (false by default). Of course, deep: true comes at the cost of one array copy (vs zero).
When source is not reactive, it's returned as-is and no tracking occurs.

Why expose this? Because it's still useful for performance-conscious users when they want to perform an operation not well covered by built-in array functions. Examples:

Example 1: v-for uses it :)
Example 2: consider writing matrix multiplication with reactive matrices (arrays). I tested a computed that performs 10x10 reactive matrices multiplication. Using readArray gave me more than 3x boost and those are pretty small matrices!
Example 3: Object.groupBy is a new API that could become quite popular. For compat. concern it is not an array method, so can't be instrumented. Unless we patch the browser APIs, readArray is the only way to optimize it in user-land.

Open points

  1. Instrumented functions are only returned for non-readonly proxies. This is detrimental to this PR and I'm not sure why it was done. Current instrumentations fall in 2 categories:
  1. I have kept v-for very close to its current source code, sticking to the for (i = 0..) loop. Using readArray(source, true) implies a full array copy on deeply reactive arrays. I think it'd be better to use map or an iterator (also instrumented for perf, requires no O(N) allocation) or maybe readArray(source, false) and call toReactive inside the loop if required (more code).
  2. I mentioned the new Object.groupBy API above. It's unfortunate it's not an array instance method. Do we want to patch Object? It feels really bad to patch native objects, but this is an API I can imagine being used inside computed a lot in the future... so it'd be nice to have the best perf by default. Maybe let users opt into that?

Noteworthy implementation details

Further ideas

If this PR is merged, some ideas: