Vue3调用Element-plus涉及子组件v-model双向绑定props问题

发布时间 2023-11-14 15:31:18作者: chuimber

Vue3调用Element-plus涉及子组件v-model双向绑定props问题

在Vue3调用Element-plus的el-dialog组件时,碰到个很有意思的问题,el-dialog的属性值v-model直接控制对话框的显示与否,点击关闭对话框和遮罩区域,组件内部会自动更改v-model的值为false来关闭对话框。问题在于当组件作为子组件时,若v-model绑定的值为父组件的属性,该如何双向绑定。

首先明白prop遵循单向绑定,即数据只能从父组件流向子组件。意味着prop是只读的,且计算属性computed也是只读的

先了解基础的子改父,若想实现子组件更改父组件的值,可以通过emit实现

<!-- 子组件更改父组件的值 -->
<!-- 父组件 -->
<template>
<h2>我是父亲</h2>
<Child :status="isShow" @ChangeStatus="updateisShow"></Child>
</template>
<script setup>
//省略import
const isShow=ref(true)
const updateisShow=(value)=>{
     isShow.value = value;
}
</script>

<!-- 子组件 -->
<template>
<h2 v-if=props.status>我是儿子</h2>
<button @click="emit('ChangeStatus',false)"></button>
</template>
<script setup>
//省略import
const props=defineProps(['status'])
const emit = defineEmits(['ChangeStatus'])
</script>

上述实现了prop的修改的一般需求,通过修改父组件属性同步到子组件进行间接修改。但并不能直接满足我的需求,比如开头说的点击关闭对话框和遮罩区域,el-dialog组件内部会自动更改v-model的值为false来关闭对话框,可以推测子组件的内部实现为status=false(假设v-model=“status”),这样问题就来到了对v-model的直接修改,但开头说了prop是只读的,且计算属性computed也是默认只读的,如何做到修改这两种。vue官方文档指出有两种方法,一是将prop改为对象类型,二是基于该 prop 值定义一个计算属性(为了可写,提供get和set方法)

  • 一、更改prop为对象类型

    官方文档:当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递

但这种更改的主要缺陷是它允许了子组件以某种不明显的方式影响父组件的状态,可能会使数据流在将来变得更难以理解。

  • 二、基于prop定义计算属性
    定义计算属性,提供get、set方法,可以实现在直接修改v-model时,会调用set方法实现更改父组件的属性进而更新prop,推荐这种方式
<!--定义计算属性并提供get、set方法-->

<!-- 父组件 -->
<!-- dialogFormVisible作为prop传入子组件-->
<template>
<div class="btn_box">
    <el-button type="primary" @click="dialogFormVisible = true" style="border-radius: 100px;">上传<i
                                class="el-icon-upload el-icon--right"></i>
    </el-button>
</div>
    <UploadDialog :status="dialogFormVisible" :parentid="currentDirId" :userid="userId" @ChangeStatus="updatedialogFormVisible">
	</UploadDialog>
</template>
<scrpit setup>
const dialogFormVisible = ref(false)
const updatedialogFormVisible = (value) => {
    dialogFormVisible.value = value;
}    
</scrpit>

<!-- 子组件  -->
<!-- status属性控制对话框显示状态-->
<template>
    <el-dialog title="上传文件" v-model="status" >
        <el-upload ref="uploadRef" multiple :limit="3" action="" :http-request="uploadFile"
            :file-list="list_data.upload_fileList" :auto-upload="false"
            style="height: 300px;width: 400px;position: relative;left: 50%;transform: translate(-50%,0);">
            <template #trigger>
                <el-button size="small" type="primary">选取文件</el-button>
            </template>
            <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传</el-button>
            <template #tip>
                <div class="el-upload__tip">单次最多只能上传3个文件</div>
            </template>
        </el-upload>
    </el-dialog>
</template>
<script setup>
const props = defineProps(['status'])
const emit = defineEmits(['ChangeStatus'])
const uploadFile = (item) => {
    let formData = new FormData();
    formData.append('parentid', props.parentid);
    formData.append('userid', props.userid)
    formData.append('file', item.file);
    axios({
        url: 'http://localhost:8088/api/uploadFile',
        method: 'post',
        headers: { "Content-Type": "multipart/form-data" },
        data: formData
    })
        .then(resp => {
            ElMessage({
                message: resp.data.msg,
                type: resp.data.code === 20000 ? 'success' : 'error'
            });
            if (resp.data.code === 20000) {
                //上传成功,调用emit通知父组件更新dialogFormVisible,进而更新prop
                emit('ChangeStatus', false)
            }
        })
    const status=computed({
    //get
    get(){
        return props.status
    },
    //set
    set(newValue){
        emit('ChangeStatus',newValue)
    }
})
}
</script>

总结

prop:父传子,数据单向传递,且随着父组件数据更新同步更新到子组件

emit:自定义事件,使父组件可以监听到自定义事件,实现子改父

v-model的原理就是prop和emit,实现双向绑定

prop只读无法修改,但是若prop为对象类型,可以修改对应地址内的属性,但无法被computed监听

computed提供set方法可以实现可写