Я немного озадачен таким поведением...
Я пытаюсь обновить значение модели в родительском элементе из дочернего...
родитель:
<template>
<form-component1 v-model = "formData1" :errors = "errors.form1" />
<button @click = "submitData">Submit</button>
</template>
<script setup>
import { reactive, watch } from 'vue';
import FormComponent1 from './FormComponent1.vue';
import { defineEmits } from 'vue';
const errors = reactive({
form1: {
name: '',
email: ''
}
});
const formData1 = reactive({
email: '',
name: ''
});
const emit = defineEmits(['submit-data']);
function submitData() {
console.info("Submitting FormData:", formData1);
emit('submit-data', { ...formData1 });
}
// Add a watch for debugging to see if formData1 is updated
watch(formData1, (newVal) => {
console.info("Parent formData1 changed:", newVal);
}, { deep: true });
</script>
Ребенок:
<template>
<div>
<input v-model = "localData.name" placeholder = "Name">
<div v-if = "errors.name">{{ errors.name }}</div>
<input v-model = "localData.email" placeholder = "Email">
<div v-if = "errors.email">{{ errors.email }}</div>
</div>
</template>
<script setup>
import { ref, watch, watchEffect, defineProps, defineEmits } from 'vue';
const props = defineProps({
modelValue: Object,
errors: Object
});
const emit = defineEmits(['update:modelValue']);
const localData = ref({ ...props.modelValue });
// React to external changes in modelValue
watch(() => props.modelValue, (newVal) => {
localData.value = { ...newVal };
}, { deep: true });
// Emit local changes to the parent
watchEffect(() => {
emit('update:modelValue', { ...localData.value });
});
</script>
Но я вижу эти значения в watchEffect в дочернем элементе... но ничего не становится родительским...
Рабочий пример: VuePlayground





v-model переназначает значение, для этого требуется, чтобы formData1 был ссылкой, а не реактивным объектом:
const formData1 = ref({
email: '',
name: ''
});
function submitData() {
emit('submit-data', { ...formData1.value });
}
Может потребоваться объединить объект вместо создания нового, чтобы избежать нежелательных обновлений состояния при синхронизации localData состояния (демо):
watch(() => props.modelValue, (newVal) => {
Object.assign(localData.value, newVal);
}, { deep: true });
Более чистым и эффективным способом было бы отбросить локальное состояние в дочернем элементе, если оно не требуется по какой-либо другой причине, например. отменить и выполнить мутации только в родительском элементе. Это одна из форм, которую может принимать двусторонняя привязка данных. Изменения легко отслеживаются в инструментах разработчика и хороши для производительности, не создаются лишние объекты (демо).
У ребенка это в основном:
<input :value = "props.value.name" @input = "emit('changeForm', { name: $event.target.value })">
В родителе:
<child :value = "formData" @changeForm = "Object.assign(formData, $event)">
@Mr.P Речь идет о следовании лучшим практикам, а не просто вопрос вкуса. Идея v-модели и двусторонней привязки в целом заключается в том, чтобы избежать мутации свойств, поскольку это приводит к беспорядочному потоку данных, см. vuejs.org/guide/comComponents/…. Это то, что у нас было 10 лет назад с Angular 1.x, и в больших масштабах оно не работало. Если по какой-то причине ответ вам не подходит, рассмотрите возможность обновления вопроса.
код выглядит намного чище с приведенным выше решением... не могли бы вы поделиться ссылкой на свою игровую площадку, пожалуйста?
@Mr.P Трудно отправить ссылку на игровую площадку через комментарии, добавил ее в пост. Проблема, которая, вероятно, была здесь неприятной, заключается в том, что localData.value= может вызвать циклическое обновление состояния при синхронизации состояний. Речь идет о том, чтобы работать чище, а не выглядеть чище. Изменение реквизита может оказаться сложной задачей для отладки и оптимизации. Я бы предложил проверить историю двусторонней привязки в ее нынешнем состоянии и понять, почему она стала такой вещью в Angular и React, а затем в Vue, который заимствовался у обоих. Если вы этого не сделали, есть причина, по которой они догматичны в отношении выдача события
@Mr.P В таком случае я использую более чистый и эффективный способ — отказаться от состояния localData, поэтому вам не нужно синхронизировать два из них, а мутировать только в родительском элементе, если только нет логики, которая этого требует ( отмена редактирования и т. д.). Т.е. у ребенка это в основном <input :value = "props.formData.name" @input = "emit('changeForm', { name: $event }, а у родителя <child :formData = "formData" @changeForm = "Object.assign(formData, $event)">. Изменения легко отслеживаются в инструментах разработчика и хороши для производительности, имеют смысл для сложных вложенных форм.
позвольте мне проверить это... если вы можете привести пример на детской площадке, я был бы вам в долгу :)
@Mr.P Я добавил это. Преимущество заключается в том, что атомарные изменения можно отлаживать в прослушивателе ChangeForm и просматривать в списке событий в инструментах разработчика, наблюдателя может быть недостаточно, чтобы распутать, если что-то пойдет не так.
ссылки не работают :(
хммм, я все еще немного борюсь... Я создал лучшую демо-версию игровой площадки... не могли бы вы взглянуть на нее? спасибо большое stackblitz.com/edit/…
Смотрите консоль для ошибок. В родителе нет базовых данных. Вы используете comps вместо <input>, поэтому реквизиты и события различаются. Сторонние компиляции обычно следуют соглашению v-model, поэтому должно быть <InputText :modelValue = "data.name" @update:modelValue = "$emit('changeForm', @input = "$emit('changeForm', { name: $event }, без target.value, см. vuejs.org/guide/comComponents/v-model.html
там тоже красиво и очень чисто.. спасибо @Estus Flask
Передайте данные формы в качестве реквизита и используйте toRefs:
<form-component1 :data = "formData1" :errors = "errors.form1" />
<button @click = "submitData">Submit</button>
<div>{{ formData1 }}</div>
</template>
<script setup>
import { reactive } from 'vue';
import FormComponent1 from './FormComponent1.vue';
import { defineEmits } from 'vue';
const errors = reactive({
form1: {
name: '',
email: ''
}
});
const formData1 = reactive({
email: '',
name: ''
});
const emit = defineEmits(['submit-data']);
function submitData() {
console.info("Submitting FormData:", formData1);
emit('submit-data', { ...formData1 });
}
</script>
<template>
<div>
<input v-model = "name" placeholder = "Name">
<div v-if = "errors.name">{{ errors.name }}</div>
<input v-model = "email" placeholder = "Email">
<div v-if = "errors.email">{{ errors.email }}</div>
</div>
</template>
<script setup>
import {toRefs} from 'vue';
const props = defineProps({
errors: Object,
data: Object
});
const {name, email} = toRefs(props.data);
</script>
Это приводит к глубокой мутации реквизита, что не рекомендуется.
тоже работает... но другой вариант мне нравится больше :) в моей реальной реализации (не в этой демо) он работает не так хорошо, как приведенный выше... в любом случае - еще раз спасибо