Overview
Vue 3's reactivity system, utilizing ES6 Proxies, is designed for seamless data handling, catering to most scenarios. However, when faced with large datasets, such as those in financial dashboards or extensive log viewers, this system can become a significant performance hindrance.
Understanding the Reactivity Challenge
Consider a scenario where you fetch a large dataset, say 50,000 log objects, and store them using ref in Vue 3. Each object, with multiple properties, gets wrapped in a Proxy, leading to massive memory consumption and browser slowdown. This deep reactivity is unnecessary if you don't need to mutate individual object properties.
<script setup>
import { ref, onMounted } from 'vue'
const logs = ref([])
onMounted(async () => {
const response = await fetch('/api/system-logs')
logs.value = await response.json()
})
</script>
Opting for Shallow Reactivity
For large arrays where you don't need deep property tracking, switch to shallowRef to improve performance.
<script setup>
import { shallowRef, onMounted } from 'vue'
const logs = shallowRef([])
onMounted(async () => {
const response = await fetch('/api/system-logs')
logs.value = await response.json()
})
</script>
This change minimizes Proxy generation, drastically reducing processing time from hundreds of milliseconds to just a few.
Managing Mixed Reactivity with markRaw
In cases where you have a reactive object but need to handle a large dataset without deep reactivity, markRaw is your solution.
<script setup>
import { reactive, markRaw } from 'vue'
const state = reactive({
isLoading: false,
filterQuery: '',
largeDataset: markRaw([])
})
const loadData = async () => {
state.isLoading = true
const data = await fetchLargeData()
state.largeDataset = markRaw(data)
state.isLoading = false
}
</script>
markRaw prevents Vue from wrapping the dataset in Proxies, reducing memory usage.
Overcoming DOM Rendering Bottlenecks
Even after optimizing reactivity, rendering thousands of DOM elements remains challenging. This is where virtualization or windowing comes into play. Only visible DOM nodes are rendered, improving performance.
Implementing Virtualization
Utilize libraries like @vueuse/core to implement virtualization effectively.
<script setup>
import { shallowRef } from 'vue'
import { useVirtualList } from '@vueuse/core'
const massiveList = shallowRef(Array.from({ length: 50000 }, (_, i) => ({
id: i,
text: `Item ${i}`
})))
const { list, containerProps, wrapperProps } = useVirtualList(
massiveList,
{
itemHeight: 40,
overscan: 10
}
)
</script>
<template>
<div v-bind="containerProps" style="height: 400px; overflow-y: auto;">
<div v-bind="wrapperProps">
<div
v-for="item in list"
:key="item.data.id"
style="height: 40px;"
>
{{ item.data.text }}
</div>
</div>
</div>
</template>
Conclusion
Vue 3's reactivity system is robust, but not always optimal for large datasets. Use shallowRef for arrays that don't need deep reactivity and markRaw for embedding large datasets in reactive objects. Always virtualize when rendering extensive lists to maintain smooth performance.
Share Your Experience
Have you encountered similar challenges with Vue 3's reactivity? Share your solutions in the comments below!