vue3如何进行真正意义上的父组件与子组件的双向绑定
这里提供两种方法,父子组件如下
父组件FatherComponent
,子组件ChildrenComponent
:
//父组件
<FatherComponent v-model"data" />
//子组件
<el-input
v-model="inputValue"
/>
...
const props = defineProps({ data: String })
const inputValue = ref(props.modelValue || '')
方法一 利用computed
//父组件
<template>
<div>
<ChildrenComponent v-model="data"></ChildrenComponent>
</div>
</template>
<script>
import { ref } from 'vue'
import ChildrenComponent from './ChildrenComponent.vue'
export default {
name: "FatherComponent",
components: {
ChildrenComponent
},
setup () {
const data = ref('aaa')
return {
data
}
}
};
</script>
...
//子组件
<template>
<div>
<el-input v-model="value"></el-input>
</div>
</template>
<script>
export default {
name: "ChildrenComponent",
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
value: {
get() { return this.modelValue },
set(value) { this.$emit('update:modelValue', value) }
}
}
};
</script>
这种方法有一个坑,你可以通过在set里面打印数据发现,当父子之间双向绑定的数据为基本数据类型时,能触发computed里的set方法,所以方法一的确是通过set触发emit来修改父组件内的data的。但是,当父子之间双向绑定的数据为引用类型数据的时候,比如对象和数组,这时候修改数据不会触发computed的set方法,因为在这里,computed监听的是引用类型数据的存储地址,而这个地址在数据修改时并不会改变,所以不会触发set方法,也就是不会调用this.$emit('update:modelValue', value)去通知父组件修改数据。但是你依然能看到,父组件数据的确被修改了,这是因为这时,在子组件中,<el-input v-model="value"></el-input>
等价于<el-input v-model="props.modelValue"></el-input>
,相当于你是自己在子组件修改了props的数据。
虽然这种方式依然能实现我们想要的效果,但是我个人并不推荐,用官方的解释就是,所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
方法二 利用:modelValue和@update:modelValue
首先,对于在组件中使用v-model,实际上等价于同时使用:modelValue和@update:modelValue,因为v-model只是一个语法糖,在代码背后,模板编译器会对 v-model
进行更冗长的等价展开,所以我们可以这样写:
//父组件
<template>
<div>
<ChildrenComponent v-model="Obj"></ChildrenComponent>
</div>
</template>
<script>
import { ref } from 'vue'
import ChildrenComponent from './ChildrenComponent.vue'
export default {
name: "FatherComponent",
components: {
ChildrenComponent
},
setup () {
const Obj = ref({data:''})
return {
Obj
}
}
};
</script>
...
//子组件
<template lang="ts">
<div>
<el-input :model-value="modelValue.data"
@update:modelValue="handleValueChange(value,'data')">
</el-input>
</div>
</template>
<script>
export default {
name: "ChildrenComponent",
props: {
modelValue: {
type: Object,
},
},
emits: ['update:modelValue'],
setup(props, { emit }){
const handleValueChange = (value: any,field: string) => {
emit("update:modelValue", { ...props.modelValue, [field]: value })
}
}
};
</script>
这种方式即使父子之间传递的是引用对象数据,也会触发emit去通知父组件数据更新,而不是在子组件内部直接修改props的数据
总结
如果父子之间双向绑定的是基本数据类型,那么两种方法都可以使用,如果父子之间双向绑定的是引用数据类型,那么第二种方法我觉得要合适一些。
转载自:https://juejin.cn/post/7213557559730978872