Организовать обмен [реактивными] данными между компонентами и модулями во Vue 3 приложении можно несколькими способами.
1. Prop drilling
Prop drilling - это ситуация, когда пропсы передаются через несколько компонентов, которые сами их не используют, только чтобы доставить данные до компонента, которому они действительно нужны.
Рассмотрим пример:
<script setup>
const user = {
  name: 'John',
  email: 'john@example.com'
}
</script>
<template>
  <div>
    <Header :user="user" />
  </div>
</template><!-- Header.vue -->
<script setup>
const props = defineProps(['user'])
</script>
<template>
  <header>
    <Nav :user="user" />
  </header>
</template><script setup>
const props = defineProps(['user'])
</script>
<template>
  <nav>
    <UserMenu :user="user" />
  </nav>
</template><!-- UserMenu.vue -->
<script setup>
const props = defineProps(['user'])
</script>
<template>
  <div>
    {{ user.name }}
  </div>
</template>Как видно, объект user передается через несколько компонентов, пока не достигает UserMenu, где он действительно используется. Это и есть prop drilling.
Схема передачи данных при prop drilling:
App.vue (:user)
   |
   +-- Header.vue (:user)
       |
       +-- Nav.vue (:user)
           |
           +-- UserMenu.vue (использует user)
Плюсы:
- Явная передача данных 
- Легко отследить поток данных 
- Не требует дополнительных инструментов 
- Хорошо работает для небольших приложений 
Минусы:
- Загромождает код при большом количестве пропсов 
- Усложняет рефакторинг 
- Компоненты-посредники получают ненужные им пропсы 
- Сложно поддерживать при глубокой вложенности 
2. Provide/Inject
Vue предоставляет встроенный механизм provide / inject для передачи данных через несколько уровней компонентов:
<!-- App.vue -->
<script setup>
import { provide } from 'vue'
const user = {
  name: 'John',
  email: 'john@example.com'
}
provide('user', user)
</script><!-- UserMenu.vue -->
<script setup>
import { inject } from 'vue'
const user = inject('user')
</script>
<template>
  <div>
    {{ user.name }}
  </div>
</template>Схема работы provide/inject:
App.vue
   |
   +-- provide('user')
   |
   +-- Header.vue
       |
       +-- Nav.vue
           |
           +-- UserMenu.vue
               |
               +-- inject('user')
Плюсы:
- Встроенное решение Vue 
- Простота использования 
- Не требует дополнительных зависимостей 
- Подходит для передачи данных через много уровней 
Минусы:
- Сложно отследить источник данных 
- Нет поддержки Vue DevTools 
- Возможны конфликты имен инъекций 
- Нет встроенной типизации (требуется дополнительная настройка TypeScript) 
3. Composition API
Другой подход - использование Vue Reactivity API напрямую, с экспортом реактивного стейта из js модуля:
// useUser.js
import { ref } from 'vue'
export const user = ref({
  name: 'John',
  email: 'john@example.com'
})
export function updateUser(newUser) {
// ...
}<!-- UserMenu.vue -->
<script setup>
import { user } from './useUser'
</script>
<template>
  <div>
    {{ user.name }}
  </div>
</template>Данный подход иногда называется глобальными рефами или модульными рефами.
Схема работы Composition API:
useUser.js (ref + методы)
    |
    +-- ComponentA.vue (импорт + использование)
    |
    +-- ComponentB.vue (импорт + использование)
    |
    +-- ComponentC.vue (импорт + использование)
Можно держать в одном файле и сам ref, и логику его работы, получив аналог объекта ООП.
С данной конструкцией можно попасть на цикличные js импорты - если А импортирует Б, в котором определен б = ref(), и Б импортирует С, который использует б (например, в watch), то в С получится "не определенный" б.
Явное создание синглтона будет более грамотным решением (например, создание composable функции и export/provide её на все приложение, либо другие вариации).
Плюсы:
- Отличная инкапсуляция логики 
- Позволяет в полной мере использовать возможности - Vue Reactivity API
- Легко тестировать 
- Хорошая переиспользуемость 
- Явные зависимости 
Минусы:
- Требует правильной структуризации кода 
- Нет централизованного управления состоянием 
- Сложнее отлаживать 
- Возможны цикличные зависимости 
4. Глобальное хранилище (Pinia)
Для более сложных случаев можно использовать менеджер состояний Pinia:
// stores/user.js
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', {
  state: () => ({
    user: {
      name: 'John',
      email: 'john@example.com'
    }
  })
})<!-- UserMenu.vue -->
<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>
<template>
  <div>
    {{ userStore.user.name }}
  </div>
</template>Схема работы Pinia:
stores/user.js (state + actions + getters)
    |
    +-- DevTools <--> Pinia Store
    |
    +-- ComponentA.vue (useUserStore())
    |
    +-- ComponentB.vue (useUserStore())
    |
    +-- ComponentC.vue (useUserStore())
Плюсы:
- Централизованное управление состоянием 
- Интеграция с Vue DevTools 
- Поддержка - SSR
- Встроенная поддержка TypeScript 
- Лучше подходит для работы в большой команде 
Минусы:
- Дополнительная зависимость 
- Падение производительности по сравнению с просто использованием ref 
- Избыточно для небольших приложений 
- Требует настройки 
- Больше кода для простых случаев 
5. Event Bus
Еще один подход - использование событийной шины (EventBus). Хотя во Vue 3 нет встроенного Event Bus как в Vue 2, мы можем использовать внешние библиотеки вроде mitt:
// eventBus.js
import mitt from 'mitt'
export const eventBus = mitt()
// Типизация событий (опционально)
type Events = {
  'user-updated': { name: string, email: string }
  'user-deleted': { id: number }
}
export const typedEventBus = mitt<Events>()Использование:
<!-- ComponentA.vue -->
<script setup>
import { eventBus } from './eventBus'
const sendUser = () => {
  eventBus.emit('user-updated', { 
    name: 'John',
    email: 'john@example.com'
  })
}
</script><!-- ComponentB.vue -->
<script setup>
import { eventBus } from './eventBus'
import { onMounted, onUnmounted } from 'vue'
const handleUser = (user) => {
  console.log('Получены данные пользователя:', user)
}
onMounted(() => {
  eventBus.on('user-updated', handleUser)
})
onUnmounted(() => {
  eventBus.off('user-updated', handleUser)
})
</script>Использование EventBus вместе в системой реактивности Vue, чаще всего, неоправданно и вносит путаницу в код, поэтому его иногда называют антипаттерном для фронтенда.
Есть случаи, когда EventBus может быть полезен, например, при реализации работы с WebSocket. Но в этом случае он работает как Proxy или Facade для WS клиента (чтобы сделать возможным переподключение при разрыве соединения), а не как часть реактивной системы.
Схема работы Event Bus:
eventBus.js
    |
    +-- ComponentA.vue (emit)
    |    |
    |    +-- событие 'user-updated' ----+
    |                                   |
    +-- ComponentB.vue (<---------------+)
    |    |
    |    +-- on('user-updated')
    |
    +-- ComponentC.vue
         |
         +-- on('user-updated')
Плюсы:
- Простая реализация 
- Гибкость в коммуникации между компонентами 
- Не требует иерархической связи между компонентами 
- Легко добавлять новых подписчиков 
Минусы:
- Сложно отслеживать поток данных 
- Возможны утечки памяти при неправильной очистке 
- Нет типизации событий по умолчанию 
Когда что использовать?
- 
Prop Drilling - оптимален когда: - Приложение имеет простую и неглубокую структуру компонентов 
- Количество передаваемых пропсов невелико 
- Данные нужны только в ближайших компонентах 
- Нет необходимости в сложной логике управления состоянием 
- Важна максимальная прозрачность передачи данных 
- Проект небольшой или находится в стадии прототипирования 
 
- 
Provide/Inject - хорошо подходит для: - Передачи данных через много уровней компонентов 
- Когда данные нужны только в определенной ветке дерева компонентов 
- В случаях с темизацией или локализацией 
 
- 
Composition API - оптимально когда: - Нужна переиспользуемая логика 
- Данные используются в разных частях приложения 
- Требуется инкапсуляция сложной логики 
 
- 
Pinia - лучший выбор если: - Нужно глобальное состояние 
- Нужна поддержка - SSR
- Необходима поддержка Vue DevTools 
- Важна типизация (TypeScript) 
 
- 
Event Bus - может быть полезен в случаях: - Когда основная система передачи данных построена на нем 
- Работы с WebSocket 
- Специфичных случаях 
- Простой коммуникации между несвязанными компонентами 
 
Выводы
- Prop drilling - не всегда проблема 
- Provide/inject - простое встроенное решение 
- Модульные рефы - мощный инструмент для переиспользуемой логики 
- Pinia - комплексное решение для управления состоянием 
- Event Bus - может быть полезен в специфичных случаях 
Выбор подхода зависит от конкретного случая и требований проекта. Иногда простая передача пропсов может быть самым понятным решением.
Другая интересная и полезная информация о Vue.js и фронтенде в целом на нашем Телеграм‑канале: @vuefaq
Комментарии (3)
 - shsv38204.02.2025 04:37- Использую pinia, очень удобно, для каждого раздела свой store, плюс встроенное сохранение состояния (persist) с возможностью выбора места хранения - localStorage, sessionStorage. Вообще после реакта вью очень легкий - как будто снял свинцовые сапоги 
 
           
 
simple-mortal
Не вдавался в причины, по которым это было убрано, но это был очень хороший инструмент, на мой взгляд. Очень удобно было сажать на шину несколько компонентов, если нужно, чтобы они отслеживали события кастомные. И появление всяких библиотечек говорит о том, что я не один так считаю )
А по поводу остального — работал с пиниа. Прикольная штука. Проще, чем vuex, понятнее. Но чаще юзаю просто реактивный объект небольшой. Как верно было замечено, можно логику упаковывать по разным изолированным объектикам. Единственное, чнемного раздражает — импорты во всех причинных местах и если нужно отрисовать в шаблоне, то через компьютед приходится свойство передавать. Но это мелочи.
zede
Так как это изначально не было целенаправленным функционалом Vue. Скорее случайным образом обнаруженная возможность для реализации. Просто из Vue в целом выпилили идею топорных событий (теперь они заранее регистрируются). Если так подумать то действительно смешно было наблюдать по 5-6 инстансов Vue для того чтоб сообщениями обменивались компоненты, как-то логика нарушается