Understanding the Reactivity of useAttrs in Vue 3

In Vue 3, useAttrs() is indeed reactive. When called inside a component's setup function, it returns a reactive object containing all non-prop attributes (like class, style, or event listeners) passed from the parent component. This reactivity means the object automatically updates when the parent re-renders with new attribute values, and you can track these changes usinng watchers or computed properties.

How useAttrs Works

The object returned by useAttrs() is a specialized Proxy object, not a standard ref or shallowRef. Its behavior closely resembles that of a reactive object, but with added constraints. For example, it is read-only—you cannot modify the attributes from the child component to affect the parent. It also automatically filters out any props that have been explicitly declared in the component.

Because it’s a proxy, you should avoid destructuring the returned object directly, as destructuring extracts plain values and breaks reactivity. Instead, access properties individually, like attrs.someAttribute. If you need to track a specific attribute, use a watcher on the whole object or on a computed property derived from it.

import { useAttrs, watchEffect } from 'vue';

export default {
  setup() {
    const componentAttrs = useAttrs();

    // Reactively log changes to the attrs object
    watchEffect(() => {
      console.log('Current attrs:', componentAttrs.class, componentAttrs.style);
    });

    return { componentAttrs };
  }
};

Is useAttrs a ref or a shallowRef?

No, useAttrs() is neither a ref nor a shallowRef. The returned object is a reactive Proxy that behaves similarly to a reactive() call, but with key differences:

  • Not a ref: You access properties directly (e.g., attrs.class) without needing .value. A ref wraps a value and requires .value for access in JavaScript, except in templates where it is auto-unwrapped.
  • Not a shallowRef: A shallowRef only triggers reactivity on top-level value changes. useAttrs responds to deep changes, such as modifications to a nested object attribute passed from the parent.
  • Similar to reactive: It supports direct property access, deep reactivity, and cannot be destructured without losing reactivity. However, it is read-only and excludes declared props, making it a specialized version of a reactive object.
import { useAttrs, isRef, isShallowRef, isReactive } from 'vue';

export default {
  setup() {
    const attrs = useAttrs();
    console.log('Is ref:', isRef(attrs));        // false
    console.log('Is shallow ref:', isShallowRef(attrs)); // false
    console.log('Is reactive:', isReactive(attrs));      // true (internally based on reactive)
  }
};

Comparing ref and reactive

Vue 3 provides two primary APIs for creating reactive state: ref and reactive. Each has distinct use cases and behaviors.

Data Types

  • ref works with any data type, including primitives (numbers, strings) and objects/arrays. It wraps the value in an object with a .value property.
  • reactive only works with objects and arrays (reference types). It cannot wrap primitives directly.

Accessing Values

  • For ref, you must use .value to read or write in JavaScript, except in templates where auto-unwrapping occurs.
  • For reactive, you access properties directly, like a normal object.
const count = ref(0);
console.log(count.value); // 0

const user = reactive({ name: 'Alice' });
console.log(user.name); // Alice

Destructuring Behavior

  • Destructuring a ref of a primitive type breaks reactivity because you obtain a plain value. For a ref containing an object, the internal reactivity is maintained because the object itself is handled with reactive.
  • Destructuring a reactive object directly loses reactivity. To preserve it, use toRefs first.
const user = reactive({ name: 'Alice', age: 20 });
const { name, age } = user; // name and age are not reactive

// Correct approach:
import { toRefs } from 'vue';
const { name, age } = toRefs(user); // both are refs and remain reactive

Use Cases and Preferences

ref is more versatile because it handles all data types and integrates smoothly with the Composition API. Its often preferred for simple state values (counters, toggles) and for returning data from composable functions. reactive excels when managing complex objects or arrays, such as form data or a list of items, because it provides a more direct, object-oriented syntax.

In practice, ref is used more frequently in modern Vue 3 applications due to its broader applicability and easier maintainability in composables.

Tags: vue3 useAttrs reactive ref composition-api

Posted on Tue, 22 Sep 2026 16:13:14 +0000 by rajsekar2u