Как во Vue устроено общение между компонентами?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Коммуникация между компонентами во Vue
Вво Vue есть несколько механизмов для передачи данных и сообщений между компонентами. Это один из ключевых аспектов архитектуры Vue приложения.
1. Props: родитель -> ребенок (вниз по дереву)
Props - это механизм передачи данных от родительского компонента к дочернему.
Родитель (Parent.vue):
<template>
<ChildComponent
:user="userData"
:isActive="true"
title="Hello Child"
/>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const userData = ref({ name: 'John', age: 30 })
</script>
Ребенок (ChildComponent.vue):
<template>
<div>
<h2>{{ title }}</h2>
<p>{{ user.name }} - {{ user.age }}</p>
<p v-if="isActive">Активен</p>
</div>
</template>
<script setup>
defineProps({
user: {
type: Object,
required: true
},
isActive: {
type: Boolean,
default: false
},
title: String
})
</script>
Важно: Props - однонаправленные. Ребенок НЕ может менять props.
2. Events/Emits: ребенок -> родитель (вверх по дереву)
Events используются, когда ребенок нужно отправить сообщение родителю.
Ребенок отправляет:
<template>
<button @click="notifyParent">Нажми меня</button>
</template>
<script setup>
const emit = defineEmits(['userClick', 'dataChanged'])
const notifyParent = () => {
// Отправляем событие с данными
emit('userClick', { timestamp: Date.now() })
}
const handleDataChange = (newValue) => {
emit('dataChanged', newValue)
}
</script>
Родитель слушает:
<template>
<ChildComponent
@userClick="onChildClick"
@dataChanged="onDataChanged"
/>
</template>
<script setup>
const onChildClick = (data) => {
console.log('Ребенок отправил:', data)
}
const onDataChanged = (newValue) => {
console.log('Данные изменились:', newValue)
}
</script>
3. v-model: двусторонняя привязка
Это синтаксический сахар для props + emit:
Родитель:
<template>
<SearchInput v-model="searchText" />
<p>Поиск: {{ searchText }}</p>
</template>
<script setup>
import { ref } from 'vue'
const searchText = ref('')
</script>
Ребенок (SearchInput):
<template>
<input
:value="modelValue"
@input="emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
Эквивалент (без v-model):
<SearchInput
:modelValue="searchText"
@update:modelValue="searchText = $event"
/>
4. Provide/Inject: для глубоко вложенных компонентов
Используй это, когда данные нужны компонентам далеко вниз по дереву (избегаешь "prop drilling").
Родитель (App.vue) предоставляет:
<template>
<DeepChild />
</template>
<script setup>
import { provide, ref } from 'vue'
import DeepChild from './DeepChild.vue'
const theme = ref('dark')
const toggleTheme = () => {
theme.value = theme.value === 'dark' ? 'light' : 'dark'
}
// Предоставляем данные и функции всем потомкам
provide('theme', theme)
provide('toggleTheme', toggleTheme)
</script>
Глубокий ребенок получает:
<template>
<button @click="toggleTheme">Переключить тему ({{ theme }})</button>
</template>
<script setup>
import { inject } from 'vue'
const theme = inject('theme')
const toggleTheme = inject('toggleTheme')
</script>
5. Event Bus (через mitt или собственный)
Для коммуникации между несвязанными компонентами:
Создать Event Bus (eventBus.js):
import { createApp } from 'vue'
import mitt from 'mitt'
const emitter = mitt()
export default emitter
Отправить событие:
<script setup>
import emitter from './eventBus'
const sendMessage = () => {
emitter.emit('userLoggedIn', { username: 'john' })
}
</script>
Получить событие:
<script setup>
import { onMounted, onUnmounted } from 'vue'
import emitter from './eventBus'
onMounted(() => {
emitter.on('userLoggedIn', (data) => {
console.log('Пользователь вошел:', data.username)
})
})
onUnmounted(() => {
emitter.off('userLoggedIn')
})
</script>
6. Pinia/Vuex: глобальное управление состоянием
Для сложных приложений с множеством компонентов:
Pinia Store (stores/user.js):
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref(null)
const isLoading = ref(false)
const fetchUser = async (id) => {
isLoading.value = true
const response = await fetch(`/api/users/${id}`)
user.value = await response.json()
isLoading.value = false
}
const logout = () => {
user.value = null
}
return { user, isLoading, fetchUser, logout }
})
Использование в компонентах:
<template>
<div>
<p>{{ userStore.user?.name }}</p>
<button @click="userStore.logout">Выход</button>
</div>
</template>
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
7. Computed и Watch для реактивности
Когда ребенок отслеживает изменения props:
<script setup>
import { computed, watch } from 'vue'
const props = defineProps(['initialValue'])
// Вычисляемое значение
const doubled = computed(() => props.initialValue * 2)
// Отслеживание изменений
watch(() => props.initialValue, (newValue) => {
console.log('Props изменились на:', newValue)
})
</script>
Сравнение подходов
| Способ | Когда использовать | Направление |
|---|---|---|
| Props | Родитель -> Ребенок | Вниз |
| Emit/Events | Ребенок -> Родитель | Вверх |
| v-model | Двусторонняя привязка | Туда-сюда |
| Provide/Inject | Глубоко вложенные компоненты | Вниз (через слои) |
| Event Bus | Несвязанные компоненты | Любые |
| Pinia/Vuex | Глобальное состояние | Все компоненты |
Практический пример: форма с валидацией
<!-- Parent.vue -->
<template>
<Form @submit="handleSubmit">
<FormField
v-model="formData.email"
@error="onFieldError"
type="email"
/>
</Form>
</template>
<script setup>
import { ref } from 'vue'
import Form from './Form.vue'
import FormField from './FormField.vue'
const formData = ref({ email: '' })
const errors = ref({})
const onFieldError = (fieldName, error) => {
errors.value[fieldName] = error
}
const handleSubmit = async () => {
// Отправить данные
}
</script>
Best Practices
- Используй Props для простых случаев - это самый понятный способ
- Emit для действий - когда ребенок уведомляет об изменениях
- Provide/Inject для контекста - для темы, языка, конфигурации
- Pinia для состояния - когда много компонентов нужны одни данные
- Event Bus аккуратно - может привести к путанице в больших приложениях
Выбор способа зависит от архитектуры приложения и того, как связаны компоненты. В большинстве случаев Props + Emit достаточно для правильной организации.