|
Разберём наследование атрибутов в 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>
Это даёт полный контроль над поведением компонентов и делает их более предсказуемыми и переиспользуемыми. |