Opt Out of Deep Reactivity for Performance
shallowRef & triggerRef
shallowRef only tracks reassignment of .value — useful when wrapping large objects or third-party state where deep tracking is wasted work.
What you'll learn
- Use shallowRef for big objects
- Manually trigger with triggerRef
- Know when shallow is the right call
ref makes objects deeply reactive — every nested property is wrapped in a proxy. For large structures (huge lists, immutable trees, third-party class instances), that’s wasted work. shallowRef tracks only the top-level .value assignment.
What shallowRef Does
import { shallowRef } from 'vue'
const big = shallowRef({ a: 1, nested: { b: 2 } })
big.value.nested.b = 99 // NOT reactive — no update
big.value = { a: 1, nested: { b: 99 } } // reactive — triggers update Mutating nested data does not trigger renders. Replacing the whole .value does.
triggerRef — Manually Notify
If you need to mutate nested state and still tell Vue to update, call triggerRef.
import { shallowRef, triggerRef } from 'vue'
const list = shallowRef<number[]>([])
function addMany(items: number[]) {
list.value.push(...items)
triggerRef(list) // force re-render
} When to Use shallowRef
- Wrapping large immutable structures (you replace, never mutate).
- Wrapping non-Vue state objects (chart libraries, video players).
- Storing big arrays where you only ever swap references.
shallowReactive
The reactive sibling exists too — only top-level properties of the object are reactive.
import { shallowReactive } from 'vue'
const state = shallowReactive({ a: 1, nested: { b: 2 } })
state.a = 2 // reactive
state.nested.b = 99 // not reactive A Real Use Case
Rendering 10,000 rows with a third-party grid library? Wrap the row array in shallowRef and replace it whole when filtering — Vue won’t waste cycles wrapping each row in proxies.