0


详细分析Vue3中的defineExpose(附Demo)

目录

前言

其基本知识可参考官网: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

  1. 父组件渲染子组件 Child 并通过 ref 获取子组件的实例。
  2. 子组件中的 count 和 increment 方法通过 defineExpose 暴露出来。
  3. 当点击父组件中的 “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>

本文转载自: https://blog.csdn.net/weixin_47872288/article/details/138899701
版权归原作者 码农研究僧 所有, 如有侵权,请联系我们删除。

“详细分析Vue3中的defineExpose(附Demo)”的评论:

还没有评论