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. Arefwraps a value and requires.valuefor access in JavaScript, except in templates where it is auto-unwrapped. - Not a shallowRef: A
shallowRefonly triggers reactivity on top-level value changes.useAttrsresponds 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
refworks with any data type, including primitives (numbers, strings) and objects/arrays. It wraps the value in an object with a.valueproperty.reactiveonly works with objects and arrays (reference types). It cannot wrap primitives directly.
Accessing Values
- For
ref, you must use.valueto 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
refof a primitive type breaks reactivity because you obtain a plain value. For arefcontaining an object, the internal reactivity is maintained because the object itself is handled withreactive. - Destructuring a
reactiveobject directly loses reactivity. To preserve it, usetoRefsfirst.
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.