目录
前言
其基本知识可参考官网:Vue3中的defineExpose
1. 基本知识
defineExpose 是 Vue 3 的 Composition API 中一个新的实用函数,用于在
<script setup>
语法下显式暴露组件的公共属性和方法
这在处理子组件时特别有用,允许父组件访问子组件的特定属性或方法
在 Vue 3 中,当我们使用
<script setup>
语法糖时,组件默认不会自动暴露内部的任何状态或方法给外部使用,为了显式暴露某些属性或方法,可以使用 defineExpose
示例Demo如下:
<scriptsetup>import{ ref }from'vue'const a =1const b =ref(2)defineExpose({
a,
b
})</script>
当父组件通过模板引用的方式获取到当前组件的实例,获取到的实例会像这样
{ a: number, b: number }
(ref 会和在普通实例中一样被自动解包)
2. Demo
- 父组件渲染子组件 Child 并通过 ref 获取子组件的实例。
- 子组件中的 count 和 increment 方法通过 defineExpose 暴露出来。
- 当点击父组件中的 “Access Child Methods” 按钮时,父组件可以访问并调用子组件的 count 和 increment
子组件:
<template><div><p>Count: {{ count }}</p><button@click="increment">Increment</button></div></template><scriptsetup>import{ ref }from'vue';const count =ref(0);functionincrement(){
count.value++;}// 使用 defineExpose 来暴露 count 和 incrementdefineExpose({
count,
increment,});</script>
父组件:
<template><div><Childref="childRef"/><button@click="accessChild">Access Child Methods</button></div></template><scriptsetup>import{ ref, onMounted }from'vue';import Child from'./Child.vue';const childRef =ref(null);functionaccessChild(){if(childRef.value){
console.log('Current count:', childRef.value.count);
childRef.value.increment();
console.log('Count after increment:', childRef.value.count);}}onMounted(()=>{if(childRef.value){
console.log('Child component mounted, initial count:', childRef.value.count);}});</script>
总的来说:
- defineExpose 用于在
<script setup>
中显式暴露组件内部状态和方法 - 父组件可以通过 ref 访问子组件实例并调用暴露的属性和方法
- 使用 defineExpose 可以让组件更加模块化和可控,只有显式暴露的部分才能被外部访问,增强了封装性和安全性
这个功能在组件之间需要进行复杂交互时特别有用,尤其是在大型项目中,能够显著提升代码的可读性和可维护性
3. 实战
从入门到十实战,反复在反复,结合实战的Demo加深印象
3.1 函数暴露
也可通过函数进行暴露
<template><div><button@click="open">Fetch Data</button><divv-if="detailLoading">Loading...</div><divv-else><divv-for="item in detailData.attachment1":key="item">{{ item }}</div><divv-for="item in detailData.attachment2":key="item">{{ item }}</div></div></div></template><scriptsetup>import{ ref, onMounted }from'vue'import DangerousWorkApi from'@/api/DangerousWorkApi'const detailLoading =ref(false)const detailData =ref({})const props =defineProps(['id'])const queryId ='some-query-id'constgetInfo=async()=>{
detailLoading.value =truetry{
detailData.value =await DangerousWorkApi.getDangerousWork(props.id || queryId)const attachment1 = detailData.value.attachment1;const attachment2 = detailData.value.attachment2;
detailData.value.attachment1 = attachment1.split(",");
detailData.value.attachment2 = attachment2.split(",");}finally{
detailLoading.value =false}}defineExpose({open: getInfo })onMounted(()=>{getInfo()})</script>
3.2 导入子组件数据
另外一个实战Demo加深印象
将 handleImport 方法改为接受参数,并传递 formData.chineseShipName 和 formData.shipVoyage
<template><Dialog:title="dialogTitle"v-model="dialogVisible"width="70%"><el-formref="formRef":model="formData":rules="formRules"label-width="100px"v-loading="formLoading"><el-row:gutter="20"><!-- ... 省略其它表单项 ... --></el-row></el-form><el-tabsv-model="subTabsName"><el-tab-panename="enterSite"><template#label>
危险品进场申请单
<el-buttontype="warning"plain@click="handleImport(formData.chineseShipName, formData.shipVoyage)"v-hasPermi="['dangerous:appointment-commission:enterSiteImport']"style="margin-left: 10px;"><Iconicon="ep:upload"class="mr-5px"/> 一键导入
</el-button></template><EnterSiteFormref="enterSiteFormRef":appointment-id="formData.id":form-type="formType"/></el-tab-pane></el-tabs><template#footer><el-button@click="submitForm"type="primary":disabled="formLoading"v-if="formType !== 'detail'">保 存</el-button><el-button@click="submitAndCommitForm"type="success":disabled="formLoading"v-if="formType !== 'detail'">保存并提交预约</el-button><el-button@click="dialogVisible = false"v-if="formType !== 'detail'">取 消</el-button></template></Dialog><EnterSiteImportFormref="importFormRef"@success="getList"/></template><script>import{ ref }from'vue'exportdefault{setup(){const importFormRef =ref(null)consthandleImport=(chineseShipName, shipVoyage)=>{
importFormRef.value.open(chineseShipName, shipVoyage)}return{
importFormRef,
handleImport,// ... 省略其它数据和方法 ...}}}</script>
在导入对话框组件中,修改 open 方法以接受参数,并在组件中存储这些参数以便在提交时使用
另外一个文件如下:
defineOptions({name:'EnterSiteImportForm'})const message =useMessage()// 消息弹窗const dialogVisible =ref(false)// 弹窗的是否展示const formLoading =ref(false)// 表单的加载中const uploadRef =ref()const importUrl =import.meta.env.VITE_BASE_URL+import.meta.env.VITE_API_URL+'/dangerous/enterprise-registry/enterSiteImport'const uploadHeaders =ref()// 上传 Header 头const fileList =ref([])// 文件列表const chineseShipName =ref('')// 存储中文船名const shipVoyage =ref('')// 存储船舶航次/** 打开弹窗 */constopen=(shipName, voyage)=>{
dialogVisible.value =true
fileList.value =[]
chineseShipName.value = shipName
shipVoyage.value = voyage
resetForm()}defineExpose({ open })// 提供 open 方法,用于打开弹窗/** 提交表单 */constsubmitForm=async()=>{if(fileList.value.length ==0){
message.error('请上传文件')return}// 提交请求
uploadHeaders.value ={Authorization:'Bearer '+getAccessToken(),'tenant-id':getTenantId(),'Chinese-Ship-Name': chineseShipName.value,'Ship-Voyage': shipVoyage.value
}
formLoading.value =true
uploadRef.value!.submit()}
3.3 更新子表单数据
对应的方法数据如下:
html按钮
<el-buttontype="info"plain@click="handleRefresh(formData.id)"v-hasPermi="['dangerous:appointment-commission:enterSiteRefresh']"><Iconicon="ep:refresh"class="mr-5px"/> 刷新重置
</el-button>
对应的按钮方法如下:
/** 刷新 */consthandleRefresh=async(id)=>{
formLoading.value =true;try{const fileData =await AppointmentCommissionApi.getEnterSiteListByAppointmentId(id);
console.log(fileData);// 更新子表单的数据if(enterSiteFormRef.value){
enterSiteFormRef.value.setData(fileData);}}catch(error){
console.error('Failed to fetch data:', error);}finally{
formLoading.value =false;}}
通过另外一个文件的刷新来暴露
<template><el-form
ref="formRef":model="formData":rules="formRules"
v-loading="formLoading"
label-width="0px":inline-message="true"><el-table :data="formData"class="-mt-10px"><el-table-column label="序号" type="index" width="100"/><el-table-column label="提单号" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.billNumber`":rules="formRules.billNumber"class="mb-0px!"><el-input v-model="row.billNumber":disabled="formType === 'detail'" placeholder="请输入提单号"/></el-form-item></template></el-table-column><el-table-column label="箱号" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.boxNumber`":rules="formRules.boxNumber"class="mb-0px!"><el-input v-model="row.boxNumber":disabled="formType === 'detail'" placeholder="请输入箱号"/></el-form-item></template></el-table-column><el-table-column label="尺寸" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.boxSize`":rules="formRules.boxSize"class="mb-0px!"><el-input v-model="row.boxSize":disabled="formType === 'detail'" placeholder="请输入尺寸"/></el-form-item></template></el-table-column><el-table-column label="箱型" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.boxType`":rules="formRules.boxType"class="mb-0px!"><el-input v-model="row.boxType":disabled="formType === 'detail'" placeholder="请输入箱型"/></el-form-item></template></el-table-column><el-table-column label="货名" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.productName`":rules="formRules.productName"class="mb-0px!"><el-input v-model="row.productName":disabled="formType === 'detail'" placeholder="请输入货名"/></el-form-item></template></el-table-column><el-table-column label="危险品等级" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.hazardousLevel`":rules="formRules.hazardousLevel"class="mb-0px!"><el-input v-model="row.hazardousLevel":disabled="formType === 'detail'" placeholder="请输入危险品等级"/></el-form-item></template></el-table-column><el-table-column label="危规号" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.hazardCode`":rules="formRules.hazardCode"class="mb-0px!"><el-input v-model="row.hazardCode":disabled="formType === 'detail'" placeholder="请输入危规号"/></el-form-item></template></el-table-column><el-table-column align="center" fixed="right" label="操作" width="60" v-if="formType !== 'detail'"><template #default="{ $index }"><el-button @click="handleDelete($index)" link type="primary" v-if="formType !== 'detail'">删除</el-button></template></el-table-column></el-table></el-form><el-row justify="center"class="mt-3"><el-button @click="handleAdd" round v-if="formType !== 'detail'">+ 添加危险品</el-button></el-row></template><script setup lang="ts">import{ AppointmentCommissionApi }from'@/api/dangerous/appointmentcommission'const props = defineProps<{appointmentId:undefined,// 预约编号(主表的关联字段)formType: string
}>()const formLoading =ref(false)// 表单的加载中const formData =ref([])// 表单数据const formRules =reactive({// 表单验证规则billNumber:[{required:true,message:'请输入提单号',trigger:'blur'}],boxNumber:[{required:true,message:'请输入箱号',trigger:'blur'}],boxSize:[{required:true,message:'请输入尺寸',trigger:'blur'}],boxType:[{required:true,message:'请输入箱型',trigger:'blur'}],productName:[{required:true,message:'请输入货名',trigger:'blur'}],hazardousLevel:[{required:true,message:'请输入危险品等级',trigger:'blur'}],hazardCode:[{required:true,message:'请输入危规号',trigger:'blur'}],})const formRef =ref()// 表单 Ref/** 监听主表的关联字段的变化,加载对应的子表数据 */watch(()=> props.appointmentId,async(val)=>{// 1. 重置表单
formData.value =[]// 2. val 非空,则加载数据if(!val){return;}try{
formLoading.value =true
formData.value =await AppointmentCommissionApi.getEnterSiteListByAppointmentId(val)}finally{
formLoading.value =false}},{immediate:true})/** 新增按钮操作 */consthandleAdd=()=>{if(props.formType ==='detail')return// 禁用“添加危险品”按钮const row ={id:undefined,billNumber:'',boxNumber:'',boxSize:'',boxType:'',productName:'',hazardousLevel:'',hazardCode:'',appointmentId: props.appointmentId,}
formData.value.push(row)}/** 删除按钮操作 */consthandleDelete=(index)=>{if(props.formType ==='detail')return// 禁用“删除”按钮
formData.value.splice(index,1)}/** 表单校验 */constvalidate=()=>{return formRef.value.validate()}/** 表单值 */constgetData=()=>{return formData.value
}// 设置数据的方法constsetData=(newData)=>{
formData.value = newData;};defineExpose({ validate, getData, setData })</script>
版权归原作者 码农研究僧 所有, 如有侵权,请联系我们删除。