ref makes an object deeply reactive, which walks the whole structure — expensive and pointless when the object is only ever replaced.
// every nested property gets a proxy. on 40,000 rows,
// this is measurable on every assignment.
const rows = ref([])
// only the .value assignment is tracked
const rows = shallowRef([])
rows.value = await fetchRows() // triggers
rows.value.push(row) // does NOT trigger
triggerRef(rows) // ... unless you say so
The mutation not triggering is the trade and is exactly what makes it fast, so this suits data that arrives whole and is discarded whole. Reaching for it before measuring is premature; reaching for it after a profile shows the reactivity conversion as a visible cost is one of the highest-yield changes available in a Vue application.