Коммуникация между компонентами

В Quasar, как и в Vue.js, компоненты являются независимыми строительными блоками интерфейса. Эффективное взаимодействие между ними критично для построения сложных приложений. Коммуникация может происходить несколькими способами: через props и события, Provide/Inject, Vuex (или Pinia) и Event Bus. Разберем каждый метод подробно.


Передача данных через props

Props — это основной способ передачи данных от родительского компонента к дочернему.

<template>
  <q-card>
    <child-component :title="cardTitle" :count="counter" />
  </q-card>
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const cardTitle = 'Статистика';
const counter = ref(42);
</script>

В дочернем компоненте данные принимаются через props:

<script setup>
import { defineProps } from 'vue';

const props = defineProps({
  title: String,
  count: Number
});
</script>

<template>
  <div>
    <h3>{{ title }}</h3>
    <p>Количество: {{ count }}</p>
  </div>
</template>

Ключевые моменты:

  • Props являются однонаправленными: изменения в дочернем компоненте не влияют на родителя.
  • Для реактивного двунаправленного связывания используют .sync или v-model.

Эмиссия событий из дочернего компонента

Для передачи данных от дочернего компонента к родительскому применяется метод emit.

<script setup>
import { defineEmits } from 'vue';

const emit = defineEmits(['updateCounter']);

function increment() {
  emit('updateCounter', 1);
}
</script>

<template>
  <q-btn label="Добавить" @click="increment" />
</template>

Родитель принимает событие:

<child-component @updateCounter="counter += $event" />

Особенности:

  • События позволяют сохранить изоляцию компонентов.
  • Можно использовать кастомные события для более сложной логики (например, уведомления или формы).

Provide/Inject для глобальной передачи данных

provide и inject позволяют передавать данные через несколько уровней вложенности без необходимости передавать props через каждый уровень.

// Родитель
<script setup>
import { provide, ref } from 'vue';

const theme = ref('dark');
provide('theme', theme);
</script>
// Дочерний компонент, вложенный глубоко
<script setup>
import { inject } from 'vue';

const theme = inject('theme', 'light'); // 'light' — значение по умолчанию
</script>

<template>
  <div :class="theme">
    Настройка темы
  </div>
</template>

Преимущества:

  • Удобно для глобальных настроек (тема, язык, авторизация).
  • Позволяет избежать «проброса» props через множество компонентов.

Использование Vuex или Pinia

Для крупных приложений чаще применяется централизованное хранилище состояния. Quasar по умолчанию интегрируется с Vuex, а также поддерживает Pinia.

// store.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++;
    }
  }
});

В компоненте:

<script setup>
import { useCounterStore } from '@/stores/store';

const counterStore = useCounterStore();

function add() {
  counterStore.increment();
}
</script>

<template>
  <div>
    <p>Счетчик: {{ counterStore.count }}</p>
    <q-btn label="Добавить" @click="add" />
  </div>
</template>

Особенности:

  • Центральное хранилище упрощает синхронизацию данных между множеством компонентов.
  • Подходит для больших проектов с динамическими данными.

Event Bus

Хотя в современном Vue 3 использование Event Bus считается менее предпочтительным, иногда он удобен для неструктурированных уведомлений между компонентами, особенно при отсутствии Vuex.

// bus.js
import { reactive } from 'vue';

export const EventBus = reactive({ events: {} });

export function emitEvent(name, payload) {
  if (EventBus.events[name]) {
    EventBus.events[name].forEach(cb => cb(payload));
  }
}

export function onEvent(name, cb) {
  if (!EventBus.events[name]) EventBus.events[name] = [];
  EventBus.events[name].push(cb);
}

В компоненте:

<script setup>
import { onEvent, emitEvent } from '@/bus';

onEvent('notify', (msg) => console.log('Получено уведомление:', msg));

function sendNotification() {
  emitEvent('notify', 'Новое сообщение!');
}
</script>

Особенности:

  • Позволяет компонентам общаться без родительских связей.
  • Важно аккуратно управлять подписками, чтобы не было утечек памяти.

Использование слотов для передачи контента и логики

Слоты позволяют не только передавать разметку, но и делиться логикой компонента.

<q-card>
  <template #header>
    <h3>Заголовок карточки</h3>
  </template>
  <template #default>
    <p>Основной контент карточки</p>
  </template>
</q-card>

Динамические слоты (v-slot) позволяют передавать данные из дочернего компонента в родителя:

<child-component v-slot="{ count }">
  <p>Счетчик: {{ count }}</p>
</child-component>

Преимущества:

  • Гибкая передача контента и данных.
  • Упрощает построение повторно используемых компонентов UI.

Комбинация методов

Часто для одного проекта используется несколько методов коммуникации одновременно:

  • Props + Emits для локальной передачи данных.
  • Provide/Inject для глобальных настроек.
  • Vuex/Pinia для управления сложным состоянием.
  • Слоты для динамического контента и логики.

Такой подход обеспечивает чистую архитектуру и позволяет легко масштабировать интерфейс.


Хотите, я могу подготовить детальный пример проекта на Quasar с использованием всех перечисленных способов коммуникации для наглядного учебного кейса?