Vue: наследование атрибутов Печать
Добавил(а) microsin   

Разберём наследование атрибутов в Vue 3 на примерах.

1. Базовое наследование атрибутов

По умолчанию Vue автоматически применяет все атрибуты, переданные компоненту, к корневому элементу этого компонента.

< !-- ParentComponent.vue -->
< template>
  < ChildComponent
    class="custom-class"
    title="Tooltip"
    data-test="test-id"
    @click="handleClick"
  />
< /template>

< !-- ChildComponent.vue (по умолчанию) --> < template> < div>Child Content< /div> < /template>

Результат в DOM (Document Object Model):

< div class="custom-class" title="Tooltip" data-test="test-id">
  Child Content
< /div>

2. Объект $attrs

$attrs содержит все атрибуты, которые не были объявлены как props или emits.

< !-- ChildComponent.vue -->
< template>
  < div>
    < p>Атрибуты: {{ $attrs }}< /p>
    < !-- $attrs = { class: 'custom-class',
title: 'Tooltip',
'data-test': 'test-id' } --> < /div> < /template>
< script setup>
// Если не объявлены props, все атрибуты попадут в $attrs < /script>

3. Отключение автоматического наследования

< !-- ChildComponent.vue -->
< script>
export default {
inheritAttrs: false } < /script>

< !-- Или в Composition API --> < script setup> defineOptions({
inheritAttrs: false }) < /script>

Теперь атрибуты не применяются автоматически к корневому элементу.

4. Ручное распределение атрибутов

< !-- ChildComponent.vue -->
< template>
  < div :class="$attrs.class">
    < input v-bind="$attrs" />  < !-- передаём все атрибуты на input -->
    < button :title="$attrs.title">Кнопка< /button>
  < /div>
< /template>
< script setup> defineOptions({
inheritAttrs: false }) < /script>

5. Разделение атрибутов

< !-- ChildComponent.vue -->
< template>
  < div>
    < !-- Только атрибуты для input -->
    < input v-bind="inputAttrs" />
< !-- Только атрибуты для button --> < button v-bind="buttonAttrs">Кнопка< /button> < /div> < /template>
< script setup>
import { computed } from 'vue'
defineOptions({
inheritAttrs: false })

const props = defineProps({
type: String })

// Разделяем $attrs по назначению
const inputAttrs = computed(() => ({
...$attrs, type: props.type }))

const buttonAttrs = computed(() => ({
title: $attrs.title, 'data-test': $attrs['data-test'] })) < /script>

6. Обработка слушателей событий

События также попадают в $attrs как свойства onEventName:

< !-- ChildComponent.vue -->
< template>
  < div>
    < !-- Передаём все слушатели на кнопку -->
    < button v-bind="buttonListeners">Нажми меня< /button>
  < /div>
< /template>
< script setup>
import { useAttrs, computed } from 'vue'
defineOptions({ inheritAttrs: false })

const attrs = useAttrs()

// Фильтруем только слушатели событий
const buttonListeners = computed(() => {
const listeners = {}
Object.keys(attrs).forEach(key => {
if (key.startsWith('on')) {
listeners[key] = attrs[key]
}
})
return listeners }) < /script>

7. Практический пример: кастомный input

< !-- BaseInput.vue -->
< template>
  < div class="input-wrapper">
    < label v-if="label">{{ label }}< /label>
    < input
      v-bind="filteredAttrs"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
      class="base-input"
    />
  < /div>
< /template>
< script setup> defineOptions({
inheritAttrs: false })
defineProps({
modelValue: [String, Number], label: String })
defineEmits(['update:modelValue'])

const attrs = useAttrs()

// Убираем из $attrs то, что уже обработано через props
const filteredAttrs = computed(() => {
const { class: className, style, ...otherAttrs } = attrs return {
class: ['base-input', className].filter(Boolean).join(' '),
style,
...otherAttrs
} }) < /script>
< style scoped> .base-input {
border: 1px solid #ccc;
padding: 8px; } < /style>

8. Важные нюансы

1. Приоритет: локальные атрибуты перезаписывают унаследованные.
2. Классы и стили: объединяются автоматически, а не заменяются.
3. v-bind без аргумента: привязывает все свойства объекта как атрибуты.
4. v-on без аргумента: привязывает все слушатели событий.

Когда использовать inheritAttrs: false:

✅ Создание компонентов-обёрток
✅ Компоненты с несколькими корневыми элементами (Vue 3)
✅ Когда нужен полный контроль над распределением атрибутов
✅ Создание библиотек UI компонентов

Best Practices:

1. Всегда объявляйте явно props для ясности API.
2. Используйте inheritAttrs: false для компонентов-обёрток.
3. Документируйте, какие атрибуты куда передаются.
4. Используйте TypeScript для типизации $attrs.

< script setup lang="ts">
interface MyAttrs {
title?: string
'data-test'?: string
class?: string
onClick?: () => void }

const attrs = useAttrs() as MyAttrs < /script>

Это даёт полный контроль над поведением компонентов и делает их более предсказуемыми и переиспользуемыми.