v-model is slow (hangs) when used on a big select[multiple] element
Vue version
3.4.5
Link to minimal reproduction
Steps to reproduce
- Create a select[multiple] element with a thousands of options (e.g. 25K options)
- Use v-model on it
- Try to select a big chunk of options (> 500)
What is expected?
Quick v-model update
What is actually happening?
A very noticeable hang
https://github.com/vuejs/core/assets/2431842/dd3a6f1a-81fe-42bd-9f73-19a1f685c276
System Info
No response
Any additional comments?
I'm not just testing performance, I'm actually using this in production, for reasons I won't get into. I should mention that managing the state manually (e.g. using @change) is fast.
And a similar test using @change event instead of v-model It's probably not doing exactly the same things as v-model but just to demonstrate that it updates immediately and doesn't hang
https://github.com/vuejs/core/assets/2431842/7d8e84cd-54e3-4583-b6fd-afde68c5b8e7
To a degree, that's expected.
We need to set the selected prop on each affected <option>
when the parent sets the value during the re-render that was triggered by the change event of the select. To do that, we have to loo over all option elements, and for each element, look up the option's value in the list of currently selected values passed through v-model.
This is essentially a controlled input, where the component always controls which options are selected.
Your (totally valid) workaround only uses the @change event, which 1) doesn't cause a re-render and 2) thereby skips the work of re-applying the selected
attributes to all those options through this bad loop.
We might have some room for optimization here, but to a degree v-model will always be inherently be slower than an uncontrolled select due to the additional work it does.
@LinusBorg I agree, but I think this could be optimized to the extent that it becomes unnoticeable. I believe the slowness here is caused by a slow array search in the v-model implementation.
Here's an example where I've attempted to replicate the behavior of v-model, maintaining the original value type and updating the 'selected' attribute for each option.
I could optimize it even further, but this is just an example to demonstrate how fast this can be, despite looping over all options.
using a Set would indeed be more performant, but it doesn't cover all use cases, unfortunately.
We currently use a looseEqual()
function to compare values, because v-model select does support objects, and also supports inline-defined objects like this:
<option :value="{ size: 'small' }"></option>
<option :value="{ size: 'medium' }"></option>
<option :value="{ size: 'large' }"></option>
an implementation with Set
could not support that, as these inline objects only match in shape, but not in identity.
We could, in theory, think about a modifier for some kine of "performance" (v-model.opimized
) mode that would then use a Set and leave it to the developer to insure to define values in a way that selected values can be identified through identity comparison.
But then again: Should we optimize for selects with lists so large that they are unusable anyway?
Edit: Looking at our implementation again, there could be away to use a fast path with a Set automatically for primitive values ...
Appreciate the input @LinusBorg. I actually forgot v-model supports objects as a value.
A different strategy could be using a Set to track the indexes of selected options, rather than their values. I've tested and seems to work fine, it even simplifies my previous implementation. Possibly there are more edge-cases to consider, but this could be a move in the right direction.
Not sure about the inline-defined objects though, since I'm not aware of a way to access their value. I assume v-model can internally access it, so possibly this solution could work in this case as well.