During the implementation of a custom component, I explored the documentation to learn about a behavior that previously seemed a bit magical to me: Non-Prop Attributes.
Props in Vue
props
are one of the ways you can implement component communication in Vue. It is pretty similar to React and provides an API to express the properties such as type
, required
, and default
.
As an example, let's look at an input component that receives a prop named value
.
// BaseInput.vue
<template>
<div>
<input v-bind="$attrs" :value="value">
</div>
</template>
<script>
export default {
name: 'BaseInput',
props: {
value: {
type: String,
required: true,
},
},
}
</script>
And here's a possible usage:
// MyComponent.vue
<template>
<BaseInput :value="foo" />
</template>
Non-Prop Attributes
But what if you do this?
// MyComponent.vue
<template>
<BaseInput :value="foo" :readonly="isReadOnly" />
</template>
Here readonly
is not defined in the props
definition of BaseInput
:
// BaseInput.vue
<script>
export default {
// ...
props: {
value: {
type: String,
required: true,
},
// No definition for readonly
},
// ...
}
</script>
So what happens with it?
In such cases, Vue adds these attributes to the $attrs
object accessible as an instance property. In the example above, readonly
gets added to the object as $attrs.readonly
:
{
"readonly": true
}
Attribute Inheritance
Attribute inheritance (and disabling it) can leverage the usage of Non-Prop attributes. By default, attributes are added to the root
element of your template. For example:
// BaseInput.vue
<template>
<div>
<input type="text" />
</div>
</template>
// MyComponent.vue
<BaseInput data-testid="my-component-id" />
Will result in:
<div data-testid="my-component-id">
<input type="text">
</div>
Using inheritAttrs: false
, we can opt-out of this default behavior to tell Vue that we are explicitly going to bind the attributes where we need to. In the example above, we may want to set the input
element's attributes rather than the outer div
. To do that, we add v-bind="$attrs"
to the input
element.
// BaseInput.vue
<template>
<div>
<input type="text" v-bind="$attrs" />
</div>
</template>
<script>
export default {
inheritAttrs: false,
// ...
}
</script>
Now, <BaseInput data-testid="my-component-id" />
will result in:
<div>
<input type="text" data-testid="my-component-id">
</div>
That is especially useful when all the possible attribute keys are either unknown beforehand or tedious to manually define as a prop. For instance, an input
element can have a multitude of different attributes such as value
, disabled
, type
, readonly
, and more. Another example would be to add data-testid
(or other data attributes) for testing purposes or integration with 3rd party libraries.
Note: In Vue 2, the
class
andstyle
attributes are not added to$attrs
. That means that despite usinginheritAttrs: false
, these two attributes are added to theroot
element no matter what. This behavior changed in Vue 3 by adding them to$attrs
as well.
Top comments (0)