Начальный 9 мин чтения
Компоненты Props и Emits
Props передают данные от родителя к ребёнку, emits — события от ребёнка к родителю. Это основа коммуникации между компонентами Vue.
componentspropsemitsslotsVue
Props — от родителя к ребёнку
<!-- Дочерний компонент: UserCard.vue -->
<script setup lang="ts">
interface Props {
name: string
age: number
avatar?: string
isActive?: boolean
}
const props = withDefaults(defineProps<Props>(), {
avatar: '/default-avatar.png',
isActive: true,
})
</script>
<template>
<div :class="['card', { 'card--active': props.isActive }]">
<img :src="props.avatar" :alt="props.name">
<h3>{{ props.name }}</h3>
<p>{{ props.age }} лет</p>
</div>
</template>
<!-- Родительский компонент -->
<template>
<UserCard
name="Иван Иванов"
:age="25"
:is-active="true"
/>
<!-- Привязка объекта как props -->
<UserCard v-bind="user" />
</template>
Правила props
- Props — только для чтения в дочернем компоненте
- Изменяйте локально через вычисляемое свойство или локальный ref:
<script setup>
const props = defineProps(['value'])
// ✅ Локальная копия для редактирования
const localValue = ref(props.value)
</script>
Emits — от ребёнка к родителю
<!-- Дочерний компонент: BaseModal.vue -->
<script setup lang="ts">
const emit = defineEmits<{
close: []
confirm: [value: string]
'update:title': [title: string]
}>()
function handleConfirm() {
emit('confirm', 'Подтверждено')
}
</script>
<template>
<div class="modal">
<button @click="$emit('close')">×</button>
<button @click="handleConfirm">Подтвердить</button>
</div>
</template>
<!-- Родитель -->
<template>
<BaseModal
@close="isOpen = false"
@confirm="handleConfirm"
/>
</template>
Slots — содержимое от родителя
Обычный слот
<!-- BaseCard.vue -->
<template>
<div class="card">
<slot /> <!-- Сюда попадёт содержимое от родителя -->
</div>
</template>
<!-- Использование -->
<BaseCard>
<h2>Заголовок</h2>
<p>Содержимое карточки</p>
</BaseCard>
Именованные слоты
<!-- BaseLayout.vue -->
<template>
<div class="layout">
<header>
<slot name="header">Заголовок по умолчанию</slot>
</header>
<main>
<slot /> <!-- Слот по умолчанию -->
</main>
<footer>
<slot name="footer" />
</footer>
</div>
</template>
<!-- Использование -->
<BaseLayout>
<template #header>
<h1>Моя страница</h1>
</template>
<p>Основной контент</p>
<template #footer>
<p>Подвал</p>
</template>
</BaseLayout>
Scoped slots — данные из дочернего в слот
<!-- DataList.vue — передаёт элемент в слот -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item" :index="index" />
</li>
</ul>
</template>
<!-- Родитель получает данные и решает как рендерить -->
<DataList :items="products">
<template #default="{ item, index }">
<span>{{ index + 1 }}. {{ item.name }} — {{ item.price }}₽</span>
</template>
</DataList>
provide / inject — передача через уровни
Для передачи данных глубоко вложенным потомкам без prop drilling:
<!-- Родитель (или корневой компонент) -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)
provide('updateTheme', (val) => { theme.value = val })
</script>
<!-- Любой потомок -->
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const updateTheme = inject('updateTheme', () => {}) // с дефолтом
</script>
defineExpose — публичное API компонента
<!-- InputComponent.vue -->
<script setup>
import { ref } from 'vue'
const inputRef = ref(null)
// Только то, что явно указано — доступно снаружи
defineExpose({
focus: () => inputRef.value?.focus(),
clear: () => { inputRef.value.value = '' },
})
</script>
<!-- Родитель -->
<script setup>
const inputRef = ref(null)
function focusInput() {
inputRef.value.focus()
}
</script>
<template>
<InputComponent ref="inputRef" />
<button @click="focusInput">Фокус</button>
</template>