v-model на компоненте — это синтаксический сахар для передачи значения и подписки на его изменения. Когда вы пишете:
<MyInput v-model="username" />Vue разворачивает это в:
<MyInput :modelValue="username" @update:modelValue="username = $event" />Начиная с Vue 3.4 рекомендуется использовать макрос defineModel():
// MyInput.vue — <script setup>
const model = defineModel()
// model.value — текущее значение
// Запись в model.value автоматически эмитирует update:modelValue<template>
<input :value="model" @input="model = $event.target.value" />
</template>С типизацией и опциями:
const model = defineModel({ type: String, default: '' })
// или с TypeScript:
const model = defineModel<string>({ required: true })До Vue 3.4 (и для совместимости) использовался явный паттерн:
// MyInput.vue
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])
// При изменении:
emit('update:modelValue', newValue)<input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" />На одном компоненте можно использовать несколько именованных v-model:
<UserForm
v-model:firstName="user.firstName"
v-model:lastName="user.lastName"
v-model:email="user.email"
/>// UserForm.vue
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
const email = defineModel('email')Старый паттерн для именованных моделей:
const props = defineProps(['firstName', 'lastName'])
const emit = defineEmits(['update:firstName', 'update:lastName'])v-model поддерживает модификаторы — флаги, изменяющие поведение:
<MyInput v-model.trim.uppercase="text" />// MyInput.vue
const [model, modifiers] = defineModel({
set(value) {
let v = value
if (modifiers.trim) v = v.trim()
if (modifiers.uppercase) v = v.toUpperCase()
return v
}
})Или вручную через props:
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) },
})
// props.modelModifiers.trim === true → если .trim переданЭмуляция паттерна v-model через класс: двустороннее связывание данных между "родителем" и "дочерним" компонентом
// Эмулируем механику v-model без Vue:
// родитель владеет значением, дочерний компонент
// получает его через prop и сигнализирует об изменениях через emit.
class EventEmitter {
constructor() { this._listeners = {} }
on(event, fn) {
if (!this._listeners[event]) this._listeners[event] = []
this._listeners[event].push(fn)
}
emit(event, value) {
(this._listeners[event] || []).forEach(fn => fn(value))
}
}
// "Дочерний" компонент — аналог <MyInput>
class MyInput extends EventEmitter {
constructor(modelValue = '') {
super()
this.modelValue = modelValue // prop сверху
}
// Метод, аналогичный @input="emit('update:modelValue', ...)"
userInput(newValue) {
// Применяем "модификатор" trim
const processed = this.modifiers?.trim ? newValue.trim() : newValue
this.emit('update:modelValue', processed)
}
// Настройка модификаторов (аналог v-model.trim)
setModifiers(mods) {
this.modifiers = mods
return this
}
// Когда prop обновился сверху
receiveModelValue(val) {
this.modelValue = val
console.log(` [MyInput] получил новое значение: "${val}"`)
}
}
// Именованный v-model — аналог v-model:title
class UserForm extends EventEmitter {
constructor({ firstName = '', lastName = '' } = {}) {
super()
this.firstName = firstName
this.lastName = lastName
}
changeFirstName(val) { this.emit('update:firstName', val) }
changeLastName(val) { this.emit('update:lastName', val) }
receiveProps({ firstName, lastName }) {
if (firstName !== undefined) this.firstName = firstName
if (lastName !== undefined) this.lastName = lastName
console.log(` [UserForm] props обновлены: ${JSON.stringify({ firstName: this.firstName, lastName: this.lastName })}`)
}
}
// "Родительский" компонент
class Parent {
constructor() {
this.state = { text: 'hello', firstName: 'Иван', lastName: 'Иванов' }
}
// v-model="state.text" → :modelValue + @update:modelValue
bindVModel(component) {
component.receiveModelValue(this.state.text)
component.on('update:modelValue', (val) => {
this.state.text = val
console.log(`[Parent] state.text обновлён → "${val}"`)
component.receiveModelValue(val)
})
}
// v-model:firstName + v-model:lastName
bindNamedVModel(form) {
form.receiveProps({ firstName: this.state.firstName, lastName: this.state.lastName })
form.on('update:firstName', val => {
this.state.firstName = val
console.log(`[Parent] firstName → "${val}"`)
})
form.on('update:lastName', val => {
this.state.lastName = val
console.log(`[Parent] lastName → "${val}"`)
})
}
}
// === Демо ===
const parent = new Parent()
console.log('--- v-model с модификатором .trim ---')
const input = new MyInput().setModifiers({ trim: true })
parent.bindVModel(input)
input.userInput(' World ') // trim → "World"
input.userInput(' Vue! ') // trim → "Vue!"
console.log('Финальное state.text:', parent.state.text)
console.log('\n--- Именованные v-model ---')
const form = new UserForm()
parent.bindNamedVModel(form)
form.changeFirstName('Пётр')
form.changeLastName('Петров')
console.log('Финальный state:', parent.state.firstName, parent.state.lastName)
v-model на компоненте — это синтаксический сахар для передачи значения и подписки на его изменения. Когда вы пишете:
<MyInput v-model="username" />Vue разворачивает это в:
<MyInput :modelValue="username" @update:modelValue="username = $event" />Начиная с Vue 3.4 рекомендуется использовать макрос defineModel():
// MyInput.vue — <script setup>
const model = defineModel()
// model.value — текущее значение
// Запись в model.value автоматически эмитирует update:modelValue<template>
<input :value="model" @input="model = $event.target.value" />
</template>С типизацией и опциями:
const model = defineModel({ type: String, default: '' })
// или с TypeScript:
const model = defineModel<string>({ required: true })До Vue 3.4 (и для совместимости) использовался явный паттерн:
// MyInput.vue
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])
// При изменении:
emit('update:modelValue', newValue)<input :value="props.modelValue" @input="emit('update:modelValue', $event.target.value)" />На одном компоненте можно использовать несколько именованных v-model:
<UserForm
v-model:firstName="user.firstName"
v-model:lastName="user.lastName"
v-model:email="user.email"
/>// UserForm.vue
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
const email = defineModel('email')Старый паттерн для именованных моделей:
const props = defineProps(['firstName', 'lastName'])
const emit = defineEmits(['update:firstName', 'update:lastName'])v-model поддерживает модификаторы — флаги, изменяющие поведение:
<MyInput v-model.trim.uppercase="text" />// MyInput.vue
const [model, modifiers] = defineModel({
set(value) {
let v = value
if (modifiers.trim) v = v.trim()
if (modifiers.uppercase) v = v.toUpperCase()
return v
}
})Или вручную через props:
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) },
})
// props.modelModifiers.trim === true → если .trim переданЭмуляция паттерна v-model через класс: двустороннее связывание данных между "родителем" и "дочерним" компонентом
// Эмулируем механику v-model без Vue:
// родитель владеет значением, дочерний компонент
// получает его через prop и сигнализирует об изменениях через emit.
class EventEmitter {
constructor() { this._listeners = {} }
on(event, fn) {
if (!this._listeners[event]) this._listeners[event] = []
this._listeners[event].push(fn)
}
emit(event, value) {
(this._listeners[event] || []).forEach(fn => fn(value))
}
}
// "Дочерний" компонент — аналог <MyInput>
class MyInput extends EventEmitter {
constructor(modelValue = '') {
super()
this.modelValue = modelValue // prop сверху
}
// Метод, аналогичный @input="emit('update:modelValue', ...)"
userInput(newValue) {
// Применяем "модификатор" trim
const processed = this.modifiers?.trim ? newValue.trim() : newValue
this.emit('update:modelValue', processed)
}
// Настройка модификаторов (аналог v-model.trim)
setModifiers(mods) {
this.modifiers = mods
return this
}
// Когда prop обновился сверху
receiveModelValue(val) {
this.modelValue = val
console.log(` [MyInput] получил новое значение: "${val}"`)
}
}
// Именованный v-model — аналог v-model:title
class UserForm extends EventEmitter {
constructor({ firstName = '', lastName = '' } = {}) {
super()
this.firstName = firstName
this.lastName = lastName
}
changeFirstName(val) { this.emit('update:firstName', val) }
changeLastName(val) { this.emit('update:lastName', val) }
receiveProps({ firstName, lastName }) {
if (firstName !== undefined) this.firstName = firstName
if (lastName !== undefined) this.lastName = lastName
console.log(` [UserForm] props обновлены: ${JSON.stringify({ firstName: this.firstName, lastName: this.lastName })}`)
}
}
// "Родительский" компонент
class Parent {
constructor() {
this.state = { text: 'hello', firstName: 'Иван', lastName: 'Иванов' }
}
// v-model="state.text" → :modelValue + @update:modelValue
bindVModel(component) {
component.receiveModelValue(this.state.text)
component.on('update:modelValue', (val) => {
this.state.text = val
console.log(`[Parent] state.text обновлён → "${val}"`)
component.receiveModelValue(val)
})
}
// v-model:firstName + v-model:lastName
bindNamedVModel(form) {
form.receiveProps({ firstName: this.state.firstName, lastName: this.state.lastName })
form.on('update:firstName', val => {
this.state.firstName = val
console.log(`[Parent] firstName → "${val}"`)
})
form.on('update:lastName', val => {
this.state.lastName = val
console.log(`[Parent] lastName → "${val}"`)
})
}
}
// === Демо ===
const parent = new Parent()
console.log('--- v-model с модификатором .trim ---')
const input = new MyInput().setModifiers({ trim: true })
parent.bindVModel(input)
input.userInput(' World ') // trim → "World"
input.userInput(' Vue! ') // trim → "Vue!"
console.log('Финальное state.text:', parent.state.text)
console.log('\n--- Именованные v-model ---')
const form = new UserForm()
parent.bindNamedVModel(form)
form.changeFirstName('Пётр')
form.changeLastName('Петров')
console.log('Финальный state:', parent.state.firstName, parent.state.lastName)
Реализуй класс `TwoWayBinding`, который эмулирует механику v-model: метод `bind(getter, setter)` связывает компонент с внешним состоянием — getter возвращает текущее значение, setter принимает новое. Метод `getValue()` возвращает текущее значение через getter. Метод `setValue(newVal)` вызывает setter с новым значением. Дополнительно реализуй поддержку модификатора `trim`: если при создании передан объект `{ trim: true }`, значение в setValue должно обрезаться пробелами перед передачей в setter.
В конструкторе сохрани modifiers в this.modifiers. В setValue перед вызовом setter проверяй: if (this.modifiers.trim && typeof newVal === "string") newVal = newVal.trim(). Метод bind просто сохраняет ссылки на функции: this._getter = getter, this._setter = setter.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке