Props текут от родителя к ребёнку. Но что если ребёнок должен сообщить родителю о действии пользователя — клике кнопки, отправке формы, изменении значения? Для этого используются **события (emits)**.
В Vue 3 Composition API события объявляются через defineEmits:
// ChildForm.vue
import { defineEmits } from 'vue'
const emit = defineEmits(['submit', 'cancel', 'update:modelValue'])
function handleSubmit() {
emit('submit', { name: 'Alice', age: 30 })
}
function handleCancel() {
emit('cancel')
}const emit = defineEmits({
// null — без валидации
cancel: null,
// функция-валидатор payload
submit: (payload) => {
if (!payload.name) {
console.warn('submit: name is required')
return false
}
return true
}
})<!-- Parent.vue -->
<template>
<ChildForm
@submit="handleFormSubmit"
@cancel="showForm = false"
/>
</template>
<script setup>
function handleFormSubmit(payload) {
console.log('Получили:', payload.name)
}
</script>v-model — это синтаксический сахар над props + emit. По умолчанию:
modelValueupdate:modelValue// CustomInput.vue
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
emit('update:modelValue', e.target.value)
}<!-- Родитель: -->
<CustomInput v-model="searchQuery" />
<!-- эквивалентно: -->
<CustomInput :modelValue="searchQuery" @update:modelValue="searchQuery = $event" /><!-- Несколько v-model на одном компоненте -->
<UserForm v-model:name="userName" v-model:email="userEmail" />Родитель Ребёнок
| |
|------- props (данные) ------>|
| |
|<------ emit (события) -------|
| |Ребёнок никогда не изменяет props напрямую. Вместо этого он сообщает родителю через событие, и родитель сам решает — обновлять данные или нет.
Паттерн Publisher/Subscriber для компонентной коммуникации
// EventEmitter — аналог системы emit Vue
class EventEmitter {
constructor() {
this._listeners = {}
}
on(event, callback) {
if (!this._listeners[event]) {
this._listeners[event] = []
}
this._listeners[event].push(callback)
// Возвращаем функцию отписки
return () => this.off(event, callback)
}
off(event, callback) {
if (!this._listeners[event]) return
this._listeners[event] = this._listeners[event].filter(cb => cb !== callback)
}
emit(event, ...args) {
const listeners = this._listeners[event] || []
listeners.forEach(cb => cb(...args))
}
}
// Дочерний компонент — форма
function createChildForm(emitter) {
return {
// Имитируем действие пользователя
submit(value) {
console.log(`[ChildForm] отправляем: ${value}`)
emitter.emit('submit', { value, timestamp: Date.now() })
},
cancel() {
console.log('[ChildForm] отменяем')
emitter.emit('cancel')
}
}
}
// Родительский компонент
function createParent() {
const emitter = new EventEmitter()
const child = createChildForm(emitter)
// Подписываемся на события ребёнка — аналог @submit в шаблоне
emitter.on('submit', (payload) => {
console.log(`[Parent] получили submit: "${payload.value}"`)
})
emitter.on('cancel', () => {
console.log('[Parent] форма отменена')
})
return { child }
}
const parent = createParent()
// Имитируем действия пользователя
parent.child.submit('Hello, Vue!')
// [ChildForm] отправляем: Hello, Vue!
// [Parent] получили submit: "Hello, Vue!"
parent.child.cancel()
// [ChildForm] отменяем
// [Parent] форма отменена
// v-model паттерн: двустороннее связывание через emit
function createModelInput(emitter, initialValue) {
let modelValue = initialValue
// Аналог @update:modelValue
emitter.on('update:modelValue', (newValue) => {
modelValue = newValue
console.log(`[Parent] modelValue обновлён: "${newValue}"`)
})
return {
// Имитируем ввод пользователя
input(value) {
emitter.emit('update:modelValue', value)
},
getValue: () => modelValue,
}
}
const modelEmitter = new EventEmitter()
const inputComp = createModelInput(modelEmitter, '')
inputComp.input('Alice') // [Parent] modelValue обновлён: "Alice"
inputComp.input('Bob') // [Parent] modelValue обновлён: "Bob"Props текут от родителя к ребёнку. Но что если ребёнок должен сообщить родителю о действии пользователя — клике кнопки, отправке формы, изменении значения? Для этого используются **события (emits)**.
В Vue 3 Composition API события объявляются через defineEmits:
// ChildForm.vue
import { defineEmits } from 'vue'
const emit = defineEmits(['submit', 'cancel', 'update:modelValue'])
function handleSubmit() {
emit('submit', { name: 'Alice', age: 30 })
}
function handleCancel() {
emit('cancel')
}const emit = defineEmits({
// null — без валидации
cancel: null,
// функция-валидатор payload
submit: (payload) => {
if (!payload.name) {
console.warn('submit: name is required')
return false
}
return true
}
})<!-- Parent.vue -->
<template>
<ChildForm
@submit="handleFormSubmit"
@cancel="showForm = false"
/>
</template>
<script setup>
function handleFormSubmit(payload) {
console.log('Получили:', payload.name)
}
</script>v-model — это синтаксический сахар над props + emit. По умолчанию:
modelValueupdate:modelValue// CustomInput.vue
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function onInput(e) {
emit('update:modelValue', e.target.value)
}<!-- Родитель: -->
<CustomInput v-model="searchQuery" />
<!-- эквивалентно: -->
<CustomInput :modelValue="searchQuery" @update:modelValue="searchQuery = $event" /><!-- Несколько v-model на одном компоненте -->
<UserForm v-model:name="userName" v-model:email="userEmail" />Родитель Ребёнок
| |
|------- props (данные) ------>|
| |
|<------ emit (события) -------|
| |Ребёнок никогда не изменяет props напрямую. Вместо этого он сообщает родителю через событие, и родитель сам решает — обновлять данные или нет.
Паттерн Publisher/Subscriber для компонентной коммуникации
// EventEmitter — аналог системы emit Vue
class EventEmitter {
constructor() {
this._listeners = {}
}
on(event, callback) {
if (!this._listeners[event]) {
this._listeners[event] = []
}
this._listeners[event].push(callback)
// Возвращаем функцию отписки
return () => this.off(event, callback)
}
off(event, callback) {
if (!this._listeners[event]) return
this._listeners[event] = this._listeners[event].filter(cb => cb !== callback)
}
emit(event, ...args) {
const listeners = this._listeners[event] || []
listeners.forEach(cb => cb(...args))
}
}
// Дочерний компонент — форма
function createChildForm(emitter) {
return {
// Имитируем действие пользователя
submit(value) {
console.log(`[ChildForm] отправляем: ${value}`)
emitter.emit('submit', { value, timestamp: Date.now() })
},
cancel() {
console.log('[ChildForm] отменяем')
emitter.emit('cancel')
}
}
}
// Родительский компонент
function createParent() {
const emitter = new EventEmitter()
const child = createChildForm(emitter)
// Подписываемся на события ребёнка — аналог @submit в шаблоне
emitter.on('submit', (payload) => {
console.log(`[Parent] получили submit: "${payload.value}"`)
})
emitter.on('cancel', () => {
console.log('[Parent] форма отменена')
})
return { child }
}
const parent = createParent()
// Имитируем действия пользователя
parent.child.submit('Hello, Vue!')
// [ChildForm] отправляем: Hello, Vue!
// [Parent] получили submit: "Hello, Vue!"
parent.child.cancel()
// [ChildForm] отменяем
// [Parent] форма отменена
// v-model паттерн: двустороннее связывание через emit
function createModelInput(emitter, initialValue) {
let modelValue = initialValue
// Аналог @update:modelValue
emitter.on('update:modelValue', (newValue) => {
modelValue = newValue
console.log(`[Parent] modelValue обновлён: "${newValue}"`)
})
return {
// Имитируем ввод пользователя
input(value) {
emitter.emit('update:modelValue', value)
},
getValue: () => modelValue,
}
}
const modelEmitter = new EventEmitter()
const inputComp = createModelInput(modelEmitter, '')
inputComp.input('Alice') // [Parent] modelValue обновлён: "Alice"
inputComp.input('Bob') // [Parent] modelValue обновлён: "Bob"Реализуй функции `createChildComponent()` и `createParentComponent()`. `createChildComponent()` возвращает объект с: - `onEmit(event, callback)` — регистрирует обработчик события (внутренний метод для подписки) - `submit(value)` — вызывает emit события `'submit'` с переданным значением `createParentComponent(child)` принимает дочерний компонент и возвращает объект с: - `onSubmit(callback)` — подписывается на событие `'submit'` дочернего компонента - `received` — массив, куда записываются все полученные значения После вызова `child.submit(value)` соответствующий callback в родителе должен получить это значение. ``` const child = createChildComponent() const parent = createParentComponent(child) parent.onSubmit(value => console.log('received:', value)) child.submit('test') // received: test console.log(parent.received) // ['test'] ```
Для хранения listeners используй объект: listeners[event] = []. В onEmit — пуш callback в массив. В submit — найди listeners['submit'] и вызови каждый. В onSubmit — вызови child.onEmit('submit', callback) и внутри добавь value в received перед вызовом callback.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке