JAVASCRIPT

Vue 3 Fine-Grained Reactivity with shallowRef and triggerRef

Optimize performance in Vue 3 by using `shallowRef` for non-deep reactivity and `triggerRef` for explicit updates, controlling reactivity behavior.

<!-- App.vue -->
<template>
  <div>
    <h1>Shallow Ref Demo</h1>
    <p>Non-reactive object (with shallowRef): {{ state.obj.message }}</p>
    <p>Nested reactive ref (regular ref): {{ deepState.nested.value }}</p>

    <button @click="updateShallowObj">Update Shallow Object Message (won't re-render)</button>
    <button @click="forceUpdateShallowObj">Force Update Shallow Object (triggerRef)</button>
    <button @click="updateDeepRef">Update Deep Ref</button>

    <p>Updates (shallow): {{ shallowUpdates }}</p>
    <p>Updates (deep): {{ deepUpdates }}</p>
  </div>
</template>

<script setup>
import { shallowRef, triggerRef, ref, watch } from 'vue';

// 1. Using shallowRef: Only the .value property itself is reactive
const state = shallowRef({
  obj: {
    message: 'Initial Shallow Message'
  }
});

const shallowUpdates = ref(0);
watch(state, () => {
  shallowUpdates.value++;
  console.log('Shallow state changed (shallowRef watched)');
}, { deep: true }); // Even with deep: true, inner changes to object won't trigger if only 'state' itself changes.
                        // To trigger, the 'state.value' reference itself needs to change or triggerRef must be called.

const updateShallowObj = () => {
  // Modifying an inner property of the object held by shallowRef
  // This change IS NOT reactive and WILL NOT trigger a re-render
  state.value.obj.message = 'Updated Shallow Message at ' + new Date().toLocaleTimeString();
  console.log('Inner shallowRef object updated (not reactive):', state.value.obj.message);
};

const forceUpdateShallowObj = () => {
  // To make the shallowRef reactive to inner changes, we must explicitly trigger it
  state.value.obj.message = 'Forced Update Shallow Message at ' + new Date().toLocaleTimeString();
  triggerRef(state); // Forces any effects watching 'state' to re-run
  console.log('Inner shallowRef object updated and triggered:', state.value.obj.message);
};

// 2. Using a regular ref (deeply reactive by default for objects)
const deepState = ref({
  nested: {
    value: 'Initial Deep Message'
  }
});

const deepUpdates = ref(0);
watch(deepState, () => {
  deepUpdates.value++;
  console.log('Deep state changed (ref watched)');
}, { deep: true }); // deep: true here means the inner object's properties are also watched

const updateDeepRef = () => {
  // Modifying an inner property of the object held by regular ref
  // This change IS reactive and WILL trigger a re-render
  deepState.value.nested.value = 'Updated Deep Message at ' + new Date().toLocaleTimeString();
  console.log('Inner deepRef object updated (reactive):', deepState.value.nested.value);
};
</script>
How it works: In Vue 3, `shallowRef` is a powerful tool for performance optimization. Unlike `ref`, which makes its entire `.value` (if it's an object) deeply reactive, `shallowRef` only makes the `.value` property itself reactive. This means if you change a property *within* the object assigned to a `shallowRef`, Vue won't automatically detect the change or trigger updates. To explicitly force updates for changes made to nested properties of a `shallowRef`, you must use `triggerRef(shallowRefInstance)`. This technique is ideal for situations where you're working with large, immutable data structures or when you want to manually manage reactivity for specific parts of your state, reducing overhead from deep reactivity tracking.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs