В Quasar, как и в Vue.js, компоненты являются независимыми строительными блоками интерфейса. Эффективное взаимодействие между ними критично для построения сложных приложений. Коммуникация может происходить несколькими способами: через props и события, Provide/Inject, Vuex (или Pinia) и Event Bus. Разберем каждый метод подробно.
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>
Ключевые моменты:
.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 позволяют передавать
данные через несколько уровней вложенности без
необходимости передавать 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>
Преимущества:
Для крупных приложений чаще применяется централизованное хранилище состояния. 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>
Особенности:
Хотя в современном 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>
Преимущества:
Часто для одного проекта используется несколько методов коммуникации одновременно:
Такой подход обеспечивает чистую архитектуру и позволяет легко масштабировать интерфейс.
Хотите, я могу подготовить детальный пример проекта на Quasar с использованием всех перечисленных способов коммуникации для наглядного учебного кейса?