一、前言
Vue官方文档Props单向数据流讲解
Vue中遵循单向数据流
,所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件
,而不会逆向传递
。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
但是项目中总是有需求让我们来修改子组件内部传入的prop,所以才有了这篇文章,其实我们可以间接修改数据。
二、问题背景
父组件传递给子组件一个名为count数据,但是现在要在子组件中修改它的值并且实时更新页面,直接this.count是不能直接修改他的值的,控制台会报错,报错如下。所以我采用了下面两种方式间接更改。
三、解决方法
方法1:子组件通过computed计算属性来间接修改父组件传递的值
父组件传值
<GoodsBasic :renderObj='renderBasic' :count='count'></GoodsBasic>
子组件更改传入的值
<template>
<div class='goodsBasic'>
<div>{{ incrementCount}}</div>
<button @click='changeCount'>增加次数</button>
</div>
</template>
<script>
export default {
props: {
renderObj: {
type: Object,
default () {
return {}
}
},
count:{
type: Number,
default:0
}
},
data () {
return {
increment: this.count //新定义一个变量,并把prop传进来的值作为初始值
}
},
computed:{
incrementCount(){ //当新定义的变量变更时,计算属性也会自动更新
return this.increment
}
},
methods: {
changeCount(){
this.increment++
}
}
}
</script>
方法2:子组件data中重新定义个局部数据,把父组件prop传来的数据作为初始值使用。
父组件传值
<GoodsBasic :renderObj='renderBasic' :count='count'></GoodsBasic>
子组件更改传入的值
<template>
<div class='goodsBasic'>
<div>{{ increment }}</div>
<button @click='changeCount'>增加次数</button>
</div>
</template>
export default {
props: {
renderObj: {
type: Object,
default () {
return {}
}
},
count:{
type: Number,
default:0
}
},
data () {
return {
increment: this.count //作为初始值使用,这样做就使prop和后续更新无关了
}
},
methods: {
changeCount(){
this.increment++
}
}
}
</script>
四、更改对象 / 数组类型的 props
经过个人测试发现,当传入的prop为Object类型的时候,修改组件内部的prop可以对应的改变父组件中的值。如果传入的prop为简单类型(例如String,Number等)时,浏览器会报错,提示子组件不能修改prop的值。
比如上文例子更改 renderBasic.price
控制台就不会报错。
个人感觉当传入的prop为引用类型时,子组件能直接修改父组件值,是因为在堆内存中公用同一个内存地址;修改的话只是改了它的值,而内存地址并没变,所以不报错;
基本数据类型修改会报错,原因是指向的内存地址要被迫修改,所以控制台报错。
另外Vue官方文档也说了:
当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为
JavaScript 的对象和数组是按引用传递
,而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。
这种更改的主要缺陷是它允许了子组件以某种不明显的方式影响父组件的状态,可能会使数据流在将来变得更难以理解。在最佳实践中,你应该尽可能避免这样的更改,除非父子组件在设计上本来就需要紧密耦合。在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。
目前我们公司有个项目就是就是因为父子组件数据需要紧密耦合的,所以直接在子组件更改了数据;
上面文档说的子组件应该抛出一个事件来通知父组件做出改变,意思就是子组件调用$emit
抛出一个事件名,去通知父组件,值要改变了,在父组件写一个事件做后续处理。
五、开发场景 - 父子组件实现多表单项双向绑定
我们项目中有这样一个场景,封装了一个通用子组件,是需要v-for循环得到的form表单项。然后通过props将父组件的数组传递给子组件,子组件通过emit触发事件来修改父组件的值,实现多表单项双向绑定。
首先,让我们创建一个简单的示例来说明这个过程。假设有一个父组件 ParentComponent
和一个子组件 ChildComponent
,父组件中有一个数组 formData
需要传递给子组件,并且子组件需要修改 formData
中的值。
// ParentComponent.vue
<template>
<div>
<ChildComponent :formData="formData" @updateFormData="updateFormData" />
</div>
</template>
<script>
import { ref} from 'vue';
import ChildComponent from './ChildComponent.vue'
const formData = ref([ // 这里使用 ref 创建响应式对象
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
]);
// ============ 保持单项数据流 修改父组件的 formData 数组值 ============
function updateFormData({index, key, value}){
formData.value[index][key] = value;
}
</script>
// ChildComponent.vue
<template>
<div>
<div v-for="(item, index) in formData" :key="index">
<input v-model="item.name" @input="updateName(index, $event.target.value)" />
<input v-model="item.age" @input="updateAge(index, $event.target.value)" />
</div>
</div>
</template>
<script>
import { ref, withDefaults, defineProps, onMounted, defineEmits } from 'vue'
// ------------- 传入数据 -------------
interface Props{
formData: []
}
const props = withDefaults(defineProps<Props>(), {
formData: () => [],
})
// ------------- 传出方法 -------------
type Emit={
(e:'updateFormData', data:object):void
}
const emit = defineEmits<Emit>()
const updateName = (index, value) => {
emit('updateFormData', {index, 'name', value}); // 发送事件更新父组件的 formData 值
}
const updateAge = (index, value) => {
emit('updateFormData', {index, 'age', value}); // 发送事件更新父组件的 formData 值
}
</script>
当 ChildComponent
中的表单项发生变化时,通过 emit
发送 updateFormData
事件来通知父组件修改对应的数据。文章来源:https://www.toymoban.com/news/detail-611921.html
想继续深入vue传值的问题,也可以去看我之前关于vue中传值方法的文章。
vue组件之间的传值方法(父子传值,兄弟传值,跨级传值,vuex)文章来源地址https://www.toymoban.com/news/detail-611921.html
到了这里,关于vue中子组件间接修改父组件传递过来的值的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!