Vue3中子组件修改数据为何会触发父组件更新?
子组件修改非嵌套属性为何同步更新父组件?
父组件代码
<template> <h2>Parent</h2> {{ parent.a }} {{ parent.b }} <ChildComponent :data="parent" /> </template> <script setup> import { reactive } from 'vue' import ChildComponent from './components/ChildComponent.vue' const parent = reactive({ a: 1, b: 2 }) </script>
子组件代码
<template> <h2>Child component</h2> <p>{{ child.a }}</p> <p>{{ child.b }}</p> <input type="text" v-model="child.b" /> </template> <script setup> import { reactive } from 'vue' const props = defineProps(['data']) const child = reactive(props.data) child.a = 'why do i update the parent?' </script>
疑问
原本以为props是单向绑定,必须通过emit把数据回传给父组件,但子组件修改child对象时,父组件的parent对象也跟着更新了。Vue文档里提到:
当对象或数组作为 props 传递时,子组件虽然不能修改 props 的绑定关系,但可以修改对象或数组的嵌套属性。这是因为在 JavaScript 中,对象和数组是按引用传递的,Vue 要阻止这类修改的代价过高。
但我的示例里a和b都不是嵌套属性,为什么还会出现这种情况?
原因解析
- 你在子组件里执行
const child = reactive(props.data)时,并没有创建新对象——props.data本身就是父组件的parent对象(引用类型),reactive只是把这个已有对象包装成响应式,child和parent指向的是内存里的同一个对象。 - 不管是修改顶层属性(比如
a、b)还是嵌套属性,本质都是在操作同一个引用的对象,父组件的parent自然会同步变化。 - 文档里提到的“嵌套属性”只是这类引用传递场景的常见例子,不是说只有嵌套属性才会触发——只要传递的是对象/数组这类引用类型,修改它的任何层级属性都会影响原对象。
正确的解决方案
如果要避免子组件直接修改父组件状态,推荐以下几种方式:
1. 深拷贝原对象后再创建响应式
<script setup> import { reactive } from 'vue' // 可以用JSON序列化实现简易深拷贝,或者使用lodash的cloneDeep const props = defineProps(['data']) const child = reactive(JSON.parse(JSON.stringify(props.data))) // 修改child就不会影响父组件的parent了 child.a = '只会修改子组件自己的对象' </script>
2. 用toRefs拆分属性后创建独立响应式对象
<script setup> import { toRefs, reactive } from 'vue' const props = defineProps(['data']) // 把props.data的属性转为独立ref const { a, b } = toRefs(props.data) // 创建新的响应式对象,基于原属性的值 const child = reactive({ a: a.value, b: b.value }) </script>
3. 遵循单向数据流,用emit通知父组件修改
// 子组件 <script setup> import { ref } from 'vue' const props = defineProps(['data']) const emit = defineEmits(['update-data']) // 子组件内部维护独立的响应式变量 const childA = ref(props.data.a) const childB = ref(props.data.b) // 修改时触发emit通知父组件 const handleUpdateB = () => { emit('update-data', { ...props.data, b: childB.value }) } </script> <template> <h2>Child component</h2> <p>{{ childA }}</p> <p>{{ childB }}</p> <input type="text" v-model="childB" @input="handleUpdateB" /> </template>
// 父组件接收emit <template> <h2>Parent</h2> {{ parent.a }} {{ parent.b }} <ChildComponent :data="parent" @update-data="parent = $event" /> </template>
内容的提问来源于stack exchange,提问作者paddyfields
相关产品推荐
相关产品推荐

