You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue父子组件传值:v-model绑定props报错及刷新数据丢失问题

Vue子组件v-model绑定父组件Props报错及最优解决方案

错误信息

Unexpected mutation of "data" prop.eslintvue/no-mutating-props

场景描述

父组件通过API请求获取数据后传递给子组件,子组件尝试用v-model绑定输入框展示并编辑数据,但直接绑定Props触发ESLint错误;尝试在子组件mounted钩子中将Props赋值给内部变量,虽临时解决绑定问题,但页面刷新后数据丢失。


原代码片段

父组件

<script lang="ts">
import { defineComponent,ref } from 'vue';
import axios from 'axios'
import ChildComponent from '../components/ChildComponent.vue';

export default defineComponent({
    name: 'IndexPage',
    setup() {
        return {
            fixed: ref(false),
            data: []
        };
    },
    mounted() {
        this.getData();
    },
    methods: {
        getData() {
            axios.get('/api/Hotel/' + 2).then((response) => {
                this.data = response.data;
                this.fixed = true,
                    console.log(this.data);
            });
        }
    },
    components: { ChildComponent }
});
</script>

子组件(初始版本)

<template>
    <main>
        <q-card class="height: 500px; width: 500px">
      <q-card-section>
        <div class="text-h6">Terms of Agreement</div>
        <div class="q-pa-md">
    <div class="q-gutter-md" style="max-width: 300px">
      
    <div>
                <q-input filled v-model="data.message" label="Filled" />
    </div>
    </div>
  </div>
      </q-card-section>
      <q-separator />
      <q-separator />
      <q-card-actions align="right">
        <q-btn flat label="Decline" color="primary" v-close-popup />
        <q-btn flat label="Accept" color="primary" v-close-popup />
      </q-card-actions>
    </q-card>
    </main>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
    props:['data'],
    name: 'ChildComponent',
    setup() {
        return {
            text: ref(''),
        };
    },
});
</script>

最优解决方案

方案一:自定义v-model(推荐,符合单向数据流)

Vue的v-model本质是modelValue Prop + update:modelValue事件的语法糖,通过自定义实现双向绑定,严格遵守单向数据流规则。

子组件修改

<template>
  <main>
    <q-card class="height: 500px; width: 500px">
      <q-card-section>
        <div class="text-h6">Terms of Agreement</div>
        <div class="q-pa-md">
          <div class="q-gutter-md" style="max-width: 300px">
            <div>
              <q-input filled :value="modelValue.message" @input="handleInput" label="Filled" />
            </div>
          </div>
        </div>
      </q-card-section>
      <q-separator />
      <q-separator />
      <q-card-actions align="right">
        <q-btn flat label="Decline" color="primary" v-close-popup />
        <q-btn flat label="Accept" color="primary" v-close-popup />
      </q-card-actions>
    </q-card>
  </main>
</template>
<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  props: {
    modelValue: {
      type: Object,
      required: true
    }
  },
  name: 'ChildComponent',
  setup(props, { emit }) {
    const handleInput = (val: string) => {
      emit('update:modelValue', { ...props.modelValue, message: val });
    };

    return { handleInput };
  }
});
</script>

父组件修改

<script lang="ts">
import { defineComponent, ref } from 'vue';
import axios from 'axios'
import ChildComponent from '../components/ChildComponent.vue';

export default defineComponent({
  name: 'IndexPage',
  components: { ChildComponent },
  setup() {
    // 修正初始值类型,API返回为对象而非数组
    const data = ref({});
    const fixed = ref(false);

    const getData = () => {
      axios.get('/api/Hotel/' + 2).then((response) => {
        data.value = response.data;
        fixed.value = true;
        console.log(data.value);
      });
    };

    return { fixed, data, getData };
  },
  mounted() {
    this.getData();
  }
});
</script>

<!-- 父组件模板使用v-model绑定 -->
<template>
  <ChildComponent v-model="data" />
</template>

方案二:监听Props同步内部变量

若需子组件维护独立数据副本,通过watch监听Props变化,实时同步到内部变量,确保刷新后数据能重新同步。

子组件修改

<template>
  <main>
    <q-card class="height: 500px; width: 500px">
      <q-card-section>
        <div class="text-h6">Terms of Agreement</div>
        <div class="q-pa-md">
          <div class="q-gutter-md" style="max-width: 300px">
            <div>
              <q-input filled v-model="dataIn.message" label="Filled" />
            </div>
          </div>
        </div>
      </q-card-section>
      <q-separator />
      <q-separator />
      <q-card-actions align="right">
        <q-btn flat label="Decline" color="primary" v-close-popup />
        <q-btn flat label="Accept" color="primary" v-close-popup />
      </q-card-actions>
    </q-card>
  </main>
</template>
<script lang="ts">
import { defineComponent, ref, watch } from 'vue';

export default defineComponent({
  props: {
    data: {
      type: Object,
      required: true
    }
  },
  name: 'ChildComponent',
  setup(props) {
    const dataIn = ref({ ...props.data });

    // 监听Props变化,实时同步内部变量
    watch(() => props.data, (newVal) => {
      dataIn.value = { ...newVal };
    }, { deep: true, immediate: true });

    return { dataIn };
  }
});
</script>

补充说明

  1. 父组件原代码中data初始值为数组[],但API返回对象,建议改为ref({})避免类型不匹配;
  2. 方案一遵循Vue单向数据流设计,由父组件统一维护数据源,是更规范的实现方式;方案二适合子组件需要独立处理数据的场景,但需确保与父组件数据同步。

内容的提问来源于stack exchange,提问作者Daniel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 02:20:37