логотип PurpleSchool
логотип PurpleSchool

Компоненты Vue создание передача данных события и emit

Автор

Олег Марков

Введение

Vue — один из самых популярных фреймворков для построения пользовательских интерфейсов, и ключевая возможность Vue — это компоненты. Компоненты позволяют дробить интерфейс на независимые, переиспользуемые блоки, которыми легко управлять и комбинировать. В этой статье вы узнаете, как создавать компоненты во Vue, передавать в них данные, реагировать на события и отправлять данные обратно во внешние компоненты с помощью механизма emit. Я проведу вас через базовую теорию и покажу всё на примерах, чтобы после прочтения вы могли уверенно использовать компоненты в своих проектах.

Что такое компоненты во Vue

Компоненты Vue — это переиспользуемые части интерфейса со своим шаблоном, логикой и стилями. Каждый компонент изолирован: он может содержать в себе другие компоненты, получать данные от родителя и отправлять события наружу.

Вот главные преимущества работы с компонентами:

  • Повторное использование кода — один и тот же компонент можно использовать в разных местах.
  • Легче поддерживать и развивать большие приложения.
  • Ясное разделение ответственности между частями интерфейса.

Давайте сразу посмотрим, как создать свой первый компонент.

Создание компонента во Vue

Есть несколько способов определить компонент. Я начну с самого простого — глобальная и локальная регистрация.

Глобальная регистрация

Глобально зарегистрированный компонент доступен во всём приложении:

// main.js
import Vue from 'vue'

Vue.component('MyButton', {
  template: '<button>Нажми меня</button>'
})

Теперь MyButton можно использовать в любом шаблоне вашего приложения.

Локальная регистрация

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

// MyButton.vue
<template>
  <button>Нажми меня</button>
</template>

<script>
export default {
  name: 'MyButton'
}
</script>
// ParentComponent.vue
<template>
  <div>
    <MyButton />
  </div>
</template>

<script>
import MyButton from './MyButton.vue'

export default {
  components: { MyButton }
}
</script>

Здесь MyButton будет доступен только внутри ParentComponent.

Структура компонента

Компонент обычно состоит из трех частей:

  • template — разметка компонента
  • script — логика (определение данных, методов, props и т.д.)
  • style — стили (опционально)

Вот базовый пример:

<template>
  <div>
    <h2>{{ title }}</h2>
  </div>
</template>

<script>
export default {
  props: ['title'] // Ожидаем получение 'title' от родителя
}
</script>

<style scoped>
/* Стили только для этого компонента */
h2 {
  color: #42b983;
}
</style>

Передача данных между компонентами

В Vue данные можно передавать между компонентами разными способами:

  • От родителя к потомку через props.
  • От потомка к родителю через события (и emit).
  • Коммуникация между одноуровневыми или далекими компонентами с помощью внешнего хранилища (например, Vuex) или provide/inject (этот способ менее базовый, о нём кратко расскажу ближе к концу).

Передача данных вниз: props

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

Давайте посмотрим, как это работает.

Пример

<!-- ParentComponent.vue -->
<template>
  <CustomMessage text="Привет из родителя!" />
</template>

<script>
import CustomMessage from './CustomMessage.vue'

export default {
  components: { CustomMessage }
}
</script>
<!-- CustomMessage.vue -->
<template>
  <div>
    <p>{{ text }}</p>
  </div>
</template>

<script>
export default {
  props: {
    text: {
      type: String,
      required: true // Проверяет, что prop передан и он строка
    }
  }
}
</script>

Здесь строка text передается как prop из родителя дочке. Vue умеет валидировать типы пропсов, требовать их обязательное наличие, указывать значения по умолчанию.

Проверка и настройка props

Вы можете указывать тип пропса, флаг обязательности и значение по умолчанию:

props: {
  color: {
    type: String,      // Только строка
    default: 'black',  // Значение по умолчанию
    required: false    // Не обязателен
  }
}

Механизм односторонней передачи

Важно помнить: пропсы односторонние. Дочерний компонент не должен менять свой prop напрямую. Если пробовать, Vue выдаст предупреждение. Если нужно изменить данные — эмитим событие и обновляем их в родителе (этот момент рассмотрим ниже).

Передача данных вверх: события и emit

Если нужно отправить данные от дочернего компонента к родителю, используется механизм событий с помощью метода $emit.

Базовый пример

<!-- CustomButton.vue -->
<template>
  <button @click="handleClick">Кликнуть</button>
</template>

<script>
export default {
  methods: {
    handleClick() {
      // Отправляем событие 'custom-click'
      this.$emit('custom-click')
    }
  }
}
</script>
<!-- ParentComponent.vue -->
<template>
  <CustomButton @custom-click="doSomething" />
</template>

<script>
import CustomButton from './CustomButton.vue'
export default {
  components: { CustomButton },
  methods: {
    doSomething() {
      // Этот метод вызовется, когда CustomButton сэмитит событие
      // Можно здесь обновить родительские данные
      alert('Событие от потомка!')
    }
  }
}
</script>

Передача параметров вместе с событием

$emit может передавать значения. Это очень часто используется для передачи новых значений или каких-то данных.

<!-- CustomInput.vue -->
<template>
  <input :value="value" @input="onInput" />
</template>

<script>
export default {
  props: ['value'],
  methods: {
    onInput(event) {
      // Передаете новое значение ввода родителю
      this.$emit('input', event.target.value)
    }
  }
}
</script>
<!-- ParentComponent.vue -->
<template>
  <CustomInput v-model="myData" />
</template>

<script>
import CustomInput from './CustomInput.vue'
export default {
  components: { CustomInput },
  data() {
    return {
      myData: ''
    }
  }
}
</script>

Здесь используется v-model — это синтаксический сахар для события input и prop value. Благодаря emit и props компонент ведёт себя как стандартный input!

Детали работы $emit

  • $emit можно использовать в любом методе компонента.
  • Имя события — строка (Vue сам приводит к kebab-case).
  • Аргументы, переданные в emit, потом доступны в обработчике события в родителе.

Как слушать пользовательские события

Событие, сэмитенное компонентом, можно слушать на том же уровне, где используется этот компонент:

<ChildComp @my-event="onChildEvent" />

Вложенные компоненты и проксирование событий

Если у вас несколько уровней компонентов и нужно передать событие через промежуточный компонент, можно использовать проксирование emit:

// Intermediate.vue
<template>
  <Child @something="forward" />
</template>

<script>
export default {
  methods: {
    forward(payload) {
      this.$emit('something', payload) // Переправляет наверх
    }
  }
}
</script>

Это полезно, если ваш компонент — просто обёртка, и он проксирует внутренние события дальше наверх.

Слоты: ещё один способ передачи данных

Слоты позволяют вкладывать элементы между тегами компонента при его использовании:

<!-- MyModal.vue -->
<template>
  <div class="modal">
    <slot /> <!-- Здесь будут дочерние элементы -->
  </div>
</template>
<!-- ParentComponent.vue -->
<MyModal>
  <h2>Это заголовок модального окна</h2>
  <p>Детали модального окна</p>
</MyModal>

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

Scoped slots (Слоты с областью видимости)

В некоторых случаях компонент предоставляет данные для слота:

<!-- ListRenderer.vue -->
<template>
  <div>
    <slot v-for="item in items" :item="item" :key="item.id" />
  </div>
</template>

<script>
export default {
  props: ['items']
}
</script>
<!-- Родительский компонент -->
<ListRenderer :items="myItems" v-slot="{ item }">
  <div>
    {{ item.name }}
  </div>
</ListRenderer>

Теперь слот получает item прямо из дочернего компонента.

Reactivity: реактивные данные и обновления

Vue использует реактивность — когда вы изменяете данные в родителе, значения, переданные через props, автоматически обновляются в потомке. Если ребенок меняет данные через emit, родитель тоже мгновенно получает изменения.

Этот механизм лежит в основе взаимодействия всех компонентов.

Провайдер и инжект (provide/inject)

Иногда нужно передать данные сразу множеству вложенных компонентов, минуя промежуточных. Тогда вы можете использовать подход provide/inject:

// Родитель
provide() {
  return {
    themeColor: 'green'
  }
}
// Далёкий потомок
inject: ['themeColor']

Этот способ используется в продвинутых случаях, когда props и emit уже становятся неудобными.

Пример большого приложения с компонентами

Давайте разберём простой CRUD-интерфейс: список задач с добавлением и удалением.

TaskItem.vue

<template>
  <li>
    {{ task.text }}
    <button @click="remove">Удалить</button>
  </li>
</template>

<script>
export default {
  props: ['task'],
  methods: {
    remove() {
      // Сообщаем родителю, что задачу надо удалить
      this.$emit('remove-task', this.task.id)
    }
  }
}
</script>

TaskList.vue

<template>
  <ul>
    <TaskItem 
      v-for="task in tasks" 
      :key="task.id" 
      :task="task" 
      @remove-task="onRemove"
    />
  </ul>
</template>

<script>
import TaskItem from './TaskItem.vue'

export default {
  props: ['tasks'],
  components: { TaskItem },
  methods: {
    onRemove(id) {
      // Передает событие ещё выше
      this.$emit('remove-task', id)
    }
  }
}
</script>

App.vue (родитель)

<template>
  <div>
    <input v-model="newTask" @keyup.enter="addTask" placeholder="Новая задача" />
    <TaskList 
      :tasks="tasks"
      @remove-task="removeTask"
    />
  </div>
</template>

<script>
import TaskList from './TaskList.vue'

export default {
  components: { TaskList },
  data() {
    return {
      tasks: [],
      newTask: ''
    }
  },
  methods: {
    addTask() {
      if (!this.newTask.trim()) return
      this.tasks.push({
        id: Date.now(),
        text: this.newTask
      })
      this.newTask = ''
    },
    removeTask(id) {
      this.tasks = this.tasks.filter(task => task.id !== id)
    }
  }
}
</script>

Видите, как данные "спускаются" через props и "поднимаются" через события и emit? Именно так строится большинство приложений на Vue.

Итоги

Компоненты — это основа любого проекта на Vue. Вы узнали, как и зачем их создавать, как передавать данные вниз (props) и вверх (emit), как слушать события, использовать слоты и реализовывать вложенную коммуникацию. Начните с маленьких компонентов, объединяйте их и выстроите свой интерфейс по частям — это очень удобно и эффективно.


Частозадаваемые технические вопросы по теме

Как передать функцию из родителя в дочерний компонент и вызвать её там?

Передайте функцию через prop, а в дочке вызовите её как любой другой метод:

// Родитель
<MyComponent :myFunc="handleFunc" />

// Дочерний компонент
props: ['myFunc']
...
this.myFunc() // вызывает переданную функцию

Можно ли синхронизировать данные между дочерними компонентами напрямую?

Нет, рекомендовано использовать только emit и props или общее хранилище (Vuex/Pinia). Или прокинуть данные через provide/inject, если компоненты иерархически связаны.

Как отслеживать все события, исходящие от потомка?

Добавьте для компонента v-on="$listeners" (Vue 2) или v-bind="$attrs" (Vue 3) — так можно проксировать все события/props.

Как валидация props помогает избежать ошибок?

Объявляйте тип, обязательность и значения по умолчанию. Vue уведомит вас, если проп не передан или передан неверного типа. Это уменьшает "таинственные" баги.

Как обновить prop из родителя, если его значение (например, число для счетчика) изменяется внутри потомка?

Ребёнок эмитит событие с новым значением. Родитель ловит событие, меняет своё значение, а оно автоматически "спускается" в пропс обратно в дочерний компонент. Именно так работает v-model. Никогда не изменяйте проп напрямую!

Стрелочка влевоОрганизация файлов и структура проекта Vue.jsРабота с динамическими компонентами и данными в VueСтрелочка вправо

Постройте личный план изучения Vue до уровня Middle — бесплатно!

Vue — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateРазрешение конфликтов и ошибок с помощью Vue resolveИспользование query-параметров и их обработка в маршрутах VueЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхТипизация и использование TypeScript в VuejsСоздание и использование UI Kit для Vue приложенийИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueРабота со стилями и стилизацией в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueРабота с teleport для управления DOM во VueПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиРуководство по nextTick для работы с DOMСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Vue 3 и Pinia

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий