shallowRef & triggerRef

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.

4 min read Level 3/5 #vue#refs#performance
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.

effectScope — Group & Dispose Effects →