Дочерний элемент не отправляет модель родителю

Я немного озадачен таким поведением...

Я пытаюсь обновить значение модели в родительском элементе из дочернего...

родитель:

<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

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
72
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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 15.04.2024 16:38

@Mr.P Речь идет о следовании лучшим практикам, а не просто вопрос вкуса. Идея v-модели и двусторонней привязки в целом заключается в том, чтобы избежать мутации свойств, поскольку это приводит к беспорядочному потоку данных, см. vuejs.org/guide/comComponents/…. Это то, что у нас было 10 лет назад с Angular 1.x, и в больших масштабах оно не работало. Если по какой-то причине ответ вам не подходит, рассмотрите возможность обновления вопроса.

Estus Flask 15.04.2024 17:00

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

Mr.P 15.04.2024 17:18

@Mr.P Трудно отправить ссылку на игровую площадку через комментарии, добавил ее в пост. Проблема, которая, вероятно, была здесь неприятной, заключается в том, что localData.value= может вызвать циклическое обновление состояния при синхронизации состояний. Речь идет о том, чтобы работать чище, а не выглядеть чище. Изменение реквизита может оказаться сложной задачей для отладки и оптимизации. Я бы предложил проверить историю двусторонней привязки в ее нынешнем состоянии и понять, почему она стала такой вещью в Angular и React, а затем в Vue, который заимствовался у обоих. Если вы этого не сделали, есть причина, по которой они догматичны в отношении выдача события

Estus Flask 15.04.2024 17:45

@Mr.P В таком случае я использую более чистый и эффективный способ — отказаться от состояния localData, поэтому вам не нужно синхронизировать два из них, а мутировать только в родительском элементе, если только нет логики, которая этого требует ( отмена редактирования и т. д.). Т.е. у ребенка это в основном <input :value = "props.formData.name" @input = "emit('changeForm', { name: $event }, а у родителя <child :formData = "formData" @changeForm = "Object.assign(formData, $event)">. Изменения легко отслеживаются в инструментах разработчика и хороши для производительности, имеют смысл для сложных вложенных форм.

Estus Flask 15.04.2024 17:58

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

Mr.P 15.04.2024 18:37

@Mr.P Я добавил это. Преимущество заключается в том, что атомарные изменения можно отлаживать в прослушивателе ChangeForm и просматривать в списке событий в инструментах разработчика, наблюдателя может быть недостаточно, чтобы распутать, если что-то пойдет не так.

Estus Flask 15.04.2024 18:55

ссылки не работают :(

Mr.P 15.04.2024 20:21

хммм, я все еще немного борюсь... Я создал лучшую демо-версию игровой площадки... не могли бы вы взглянуть на нее? спасибо большое stackblitz.com/edit/…

Mr.P 15.04.2024 21:06

Смотрите консоль для ошибок. В родителе нет базовых данных. Вы используете 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 15.04.2024 21:29

там тоже красиво и очень чисто.. спасибо @Estus Flask

Mr.P 16.04.2024 06:41

Передайте данные формы в качестве реквизита и используйте toRefs:

ИГРОВАЯ ПЛОЩАДКА VUE SFC

  <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>

Это приводит к глубокой мутации реквизита, что не рекомендуется.

Estus Flask 15.04.2024 16:56

Другие вопросы по теме