Skip to content

Commit bc677ec

Browse files
committed
fallthrough attrs
1 parent 046ea54 commit bc677ec

File tree

3 files changed

+145
-89
lines changed

3 files changed

+145
-89
lines changed

src/.vitepress/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ const sidebar = {
151151
link: '/guide/components/registration'
152152
},
153153
{ text: 'Props', link: '/guide/components/props' },
154-
{ text: 'Non-Prop Attributes', link: '/guide/components/attrs' },
154+
{ text: 'Fallthrough Attributes', link: '/guide/components/attrs' },
155155
{ text: 'Events', link: '/guide/components/events' },
156156
{ text: 'Slots', link: '/guide/components/slots' },
157157
{

src/guide/components/attrs.md

Lines changed: 115 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,143 @@
1-
# Non-Prop Attributes
1+
---
2+
aside: deep
3+
---
24

3-
> This page assumes you've already read the [Components Basics](/guide/essentials/component-basics). Read that first if you are new to components.
5+
# Fallthrough Attributes
46

5-
A component non-prop attribute is an attribute or event listener that is passed to a component, but does not have a corresponding property defined in [props](./props) or [emits](./events.html#defining-custom-events). Common examples of this include `class`, `style`, and `id` attributes. You can access those attributes via `$attrs` property.
7+
> This page assumes you've already read the [Components Basics](/guide/essentials/component-basics). Read that first if you are new to components.
68
7-
## Attribute Inheritance
9+
A component non-prop attribute is an attribute or event listener that is passed to a component, but is not explicitly declared in [props](./props) or [emits](./events.html#defining-custom-events). Common examples of this include `class`, `style`, and `id` attributes.
810

9-
When a component returns a single root node, non-prop attributes will automatically be added to the root node's attributes. For example, in the instance of a date-picker component:
11+
These fallthrough attributes can be accessed directly in template expressions as `$attrs`:
1012

11-
```js
12-
app.component('date-picker', {
13-
template: `
14-
<div class="date-picker">
15-
<input type="datetime-local" />
16-
</div>
17-
`
18-
})
13+
```vue-html
14+
<span>Fallthrough attributes: {{ $attrs }}</span>
1915
```
2016

21-
In the event we need to define the status of the date-picker component via a `data-status` property, it will be applied to the root node (i.e., `div.date-picker`).
17+
<div class="composition-api">
2218

23-
```vue-html
24-
<!-- Date-picker component with a non-prop attribute -->
25-
<date-picker data-status="activated"></date-picker>
19+
You can also access them in `<script setup>` using the `useAttrs()` API:
2620

27-
<!-- Rendered date-picker component -->
28-
<div class="date-picker" data-status="activated">
29-
<input type="datetime-local" />
30-
</div>
21+
```vue
22+
<script setup>
23+
import { useAttrs } from 'vue'
24+
25+
const attrs = useAttrs()
26+
</script>
3127
```
3228

33-
Same rule applies to the event listeners:
29+
If not using `<script setup>`, `attrs` will be exposed as a property of the `setup()` context:
3430

35-
```vue-html
36-
<date-picker @change="submitChange"></date-picker>
31+
```js
32+
export default {
33+
setup(props, ctx) {
34+
// fallthrough attributes are exposed as ctx.attrs
35+
}
36+
}
3737
```
3838

39+
</div>
40+
41+
<div class="options-api">
42+
43+
You can also access them in JavaScript via the `$attrs` instance property:
44+
3945
```js
40-
app.component('date-picker', {
46+
export default {
4147
created() {
42-
console.log(this.$attrs) // { onChange: () => {} }
48+
console.log(this.$attrs)
4349
}
44-
})
50+
}
4551
```
4652

47-
This might be helpful when we have an HTML element with `change` event as a root element of `date-picker`.
53+
</div>
4854

49-
```js
50-
app.component('date-picker', {
51-
template: `
52-
<select>
53-
<option value="1">Yesterday</option>
54-
<option value="2">Today</option>
55-
<option value="3">Tomorrow</option>
56-
</select>
57-
`
58-
})
55+
## Attribute Inheritance
56+
57+
When a component renders a single root element, fallthrough attributes will be automatically added to the root element's attributes. For example, given a `<MyButton>` component with the following template:
58+
59+
```vue-html
60+
<!-- template of <MyButton> -->
61+
<button>click me</button>
5962
```
6063

61-
In this case, `change` event listener is passed from the parent component to the child and it will be triggered on native `<select>` `change` event. We won't need to emit an event from the `date-picker` explicitly:
64+
And a parent using this component with:
6265

6366
```vue-html
64-
<div id="date-picker" class="demo">
65-
<date-picker @change="showChange"></date-picker>
66-
</div>
67+
<MyButton class="large" />
6768
```
6869

69-
```js
70-
const app = Vue.createApp({
71-
methods: {
72-
showChange(event) {
73-
console.log(event.target.value) // will log a value of the selected option
74-
}
75-
}
76-
})
70+
The final rendered DOM would be:
71+
72+
```html
73+
<button class="large">click me</button>
74+
```
75+
76+
### `class` and `style` Merging
77+
78+
If the child component's root element already has existing `class` or `style` attributes, it will be merged with the `class` and `style` values that are inherited from the parent. Suppose we change the template of `<MyButton>` in the previous example to:
79+
80+
```vue-html
81+
<!-- template of <MyButton> -->
82+
<button class="btn">click me</button>
83+
```
84+
85+
Then the final rendered DOM would now become:
86+
87+
```html
88+
<button class="btn large">click me</button>
89+
```
90+
91+
### `v-on` Listener Inheritance
92+
93+
Same rule applies to the event listeners:
94+
95+
```vue-html
96+
<MyButton @click="onClick" />
7797
```
7898

99+
The `click` listener will be added to the root element of `<MyButton>`, i.e. the native `<button>` element. When the native `<button>` is clicked, it will trigger the `onClick` method of the parent component. If the native `<button>` already has a `click` listener bound with `v-on`, then both listeners will trigger.
100+
79101
## Disabling Attribute Inheritance
80102

81103
If you do **not** want a component to automatically inherit attributes, you can set `inheritAttrs: false` in the component's options.
82104

83-
The common scenario for disabling an attribute inheritance is when attributes need to be applied to other elements besides the root node.
105+
<div class="composition-api">
84106

85-
By setting the `inheritAttrs` option to `false`, you can control to apply to other elements attributes to use the component's `$attrs` property, which includes all attributes not included to component `props` and `emits` properties (e.g., `class`, `style`, `v-on` listeners, etc.).
107+
If using `<script setup>`, you will need to declare this option using a separate, normal `<script>` block:
86108

87-
Using our date-picker component example from the [previous section](#attribute-inheritance), in the event we need to apply all non-prop attributes to the `input` element rather than the root `div` element, this can be accomplished by using the `v-bind` shortcut.
109+
```vue
110+
<script>
111+
// use normal <script> to declare options
112+
export default {
113+
inheritAttrs: false
114+
}
115+
</script>
88116
89-
```js{5}
90-
app.component('date-picker', {
91-
inheritAttrs: false,
92-
template: `
93-
<div class="date-picker">
94-
<input type="datetime-local" v-bind="$attrs" />
95-
</div>
96-
`
97-
})
117+
<script setup>
118+
// ...setup logic
119+
</script>
98120
```
99121

100-
With this new configuration, our `data-status` attribute will be applied to our `input` element!
122+
</div>
123+
124+
The common scenario for disabling an attribute inheritance is when attributes need to be applied to other elements besides the root node.
125+
126+
By setting the `inheritAttrs` option to `false`, you can take full control over where the fallthrough attributes should be applied to. The `$attrs` object includes all attributes not included to component `props` and `emits` properties (e.g., `class`, `style`, `v-on` listeners, etc.).
127+
128+
Using our `<MyButton>` component example from the [previous section](#attribute-inheritance) - sometimes we may need to wrap the actual `<button>` element with an extra `<div>` for styling purposes:
101129

102130
```vue-html
103-
<!-- Date-picker component with a non-prop attribute -->
104-
<date-picker data-status="activated"></date-picker>
131+
<div class="btn-wrapper">
132+
<button class="btn">click me</button>
133+
</div>
134+
```
105135

106-
<!-- Rendered date-picker component -->
107-
<div class="date-picker">
108-
<input type="datetime-local" data-status="activated" />
136+
We want all fallthrough attributes like `class` and `v-on` listeners to be applied to the inner `<button>`, not the outer `<div>`. We can achieve this with `inheritAttrs: false` and `v-bind="$attrs"`:
137+
138+
```vue-html{2}
139+
<div class="btn-wrapper">
140+
<button class="btn" v-bind="$attrs">click me</button>
109141
</div>
110142
```
111143

@@ -114,25 +146,21 @@ With this new configuration, our `data-status` attribute will be applied to our
114146
Unlike single root node components, components with multiple root nodes do not have an automatic attribute fallthrough behavior. If `$attrs` are not bound explicitly, a runtime warning will be issued.
115147

116148
```vue-html
117-
<custom-layout id="custom-layout" @click="changeValue"></custom-layout>
149+
<CustomLayout id="custom-layout" @click="changeValue" />
118150
```
119151

120-
```js
121-
// This will raise a warning
122-
app.component('custom-layout', {
123-
template: `
124-
<header>...</header>
125-
<main>...</main>
126-
<footer>...</footer>
127-
`
128-
})
129-
130-
// No warnings, $attrs are passed to <main> element
131-
app.component('custom-layout', {
132-
template: `
133-
<header>...</header>
134-
<main v-bind="$attrs">...</main>
135-
<footer>...</footer>
136-
`
137-
})
152+
If `<CustomLayout>` has the following multi-root template, there will be a warning because Vue cannot be sure where to apply the fallthrough attributes:
153+
154+
```vue-html
155+
<header>...</header>
156+
<main>...</main>
157+
<footer>...</footer>
158+
```
159+
160+
The warning will be suppressed if `$attrs` is explicitly bound:
161+
162+
```vue-html{2}
163+
<header>...</header>
164+
<main v-bind="$attrs">...</main>
165+
<footer>...</footer>
138166
```

src/guide/essentials/component-basics.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,17 @@ const props = defineProps(['title'])
217217
console.log(props.title)
218218
```
219219

220+
If you are not using `<script setup>`, props should be declared using the `props` option, and the props object will be passed to `setup()` as the first argument:
221+
222+
```js
223+
export default {
224+
props: ['title'],
225+
setup(props) {
226+
console.log(props.title)
227+
}
228+
}
229+
```
230+
220231
</div>
221232

222233
A component can have as many props as you like and, by default, any value can be passed to any prop.
@@ -407,7 +418,24 @@ This documents all the events that a component emits and optionally [validate th
407418

408419
<div class="composition-api">
409420

410-
Similar to `defineProps`, `defineEmits` is also only usable in `<script setup>` and doesn't need to be imported. It returns an `emit` function that can be used to emit events in JavaScript code.
421+
Similar to `defineProps`, `defineEmits` is also only usable in `<script setup>` and doesn't need to be imported. It returns an `emit` function that can be used to emit events in JavaScript code:
422+
423+
```js
424+
const emit = defineEmits(['enlarge-text'])
425+
426+
emit('enlarge-text')
427+
```
428+
429+
If you are not using `<script setup>`, you can declare emitted events using the `emits` option. You can access the `emit` function as a property of the setup context (passed to `setup()` as the second argument):
430+
431+
```js
432+
export default {
433+
emits: ['enlarge-text'],
434+
setup(props, ctx) {
435+
ctx.emit('enlarge-text')
436+
}
437+
}
438+
```
411439

412440
</div>
413441

0 commit comments

Comments
 (0)