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>
补充说明
- 父组件原代码中
data初始值为数组[],但API返回对象,建议改为ref({})避免类型不匹配; - 方案一遵循Vue单向数据流设计,由父组件统一维护数据源,是更规范的实现方式;方案二适合子组件需要独立处理数据的场景,但需确保与父组件数据同步。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

