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

CSDN 2024-07-27 12:03:01 阅读 81

目录

前言1. 基本知识2. Demo3. 实战3.1 函数暴露3.2 导入子组件数据3.3 更新子表单数据

前言

其基本知识可参考官网:Vue3中的defineExpose

1. 基本知识

defineExpose 是 Vue 3 的 Composition API 中一个新的实用函数,用于在 <code><script setup> 语法下显式暴露组件的公共属性和方法

这在处理子组件时特别有用,允许父组件访问子组件的特定属性或方法

在 Vue 3 中,当我们使用 <script setup> 语法糖时,组件默认不会自动暴露内部的任何状态或方法给外部使用,为了显式暴露某些属性或方法,可以使用 defineExpose

示例Demo如下:

<script setup>

import { ref } from 'vue'

const a = 1

const 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>code>

</div>

</template>

<script setup>

import { ref } from 'vue';

const count = ref(0);

function increment() {

count.value++;

}

// 使用 defineExpose 来暴露 count 和 increment

defineExpose({

count,

increment,

});

</script>

父组件:

<template>

<div>

<Child ref="childRef" />code>

<button @click="accessChild">Access Child Methods</button>code>

</div>

</template>

<script setup>

import { ref, onMounted } from 'vue';

import Child from './Child.vue';

const childRef = ref(null);

function accessChild() {

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>code>

<div v-if="detailLoading">Loading...</div>code>

<div v-else>

<div v-for="item in detailData.attachment1" :key="item">{ { item }}</div>code>

<div v-for="item in detailData.attachment2" :key="item">{ { item }}</div>code>

</div>

</div>

</template>

<script setup>

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'

const getInfo = async () => {

detailLoading.value = true

try {

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%">code>

<el-form

ref="formRef"code>

:model="formData"code>

:rules="formRules"code>

label-width="100px"code>

v-loading="formLoading"code>

>

<el-row :gutter="20">code>

<!-- ... 省略其它表单项 ... -->

</el-row>

</el-form>

<el-tabs v-model="subTabsName">code>

<el-tab-pane name="enterSite">code>

<template #label>

危险品进场申请单

<el-button

type="warning"code>

plain

@click="handleImport(formData.chineseShipName, formData.shipVoyage)"code>

v-hasPermi="['dangerous:appointment-commission:enterSiteImport']"code>

style="margin-left: 10px;"code>

>

<Icon icon="ep:upload" class="mr-5px" /> 一键导入code>

</el-button>

</template>

<EnterSiteForm ref="enterSiteFormRef" :appointment-id="formData.id" :form-type="formType" />code>

</el-tab-pane>

</el-tabs>

<template #footer>

<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="formType !== 'detail'">保 存</el-button>code>

<el-button @click="submitAndCommitForm" type="success" :disabled="formLoading" v-if="formType !== 'detail'">保存并提交预约</el-button>code>

<el-button @click="dialogVisible = false" v-if="formType !== 'detail'">取 消</el-button>code>

</template>

</Dialog>

<EnterSiteImportForm ref="importFormRef" @success="getList" />code>

</template>

<script>

import { ref } from 'vue'

export default {

setup() {

const importFormRef = ref(null)

const handleImport = (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('') // 存储船舶航次

/** 打开弹窗 */

const open = (shipName, voyage) => {

dialogVisible.value = true

fileList.value = []

chineseShipName.value = shipName

shipVoyage.value = voyage

resetForm()

}

defineExpose({ open }) // 提供 open 方法,用于打开弹窗

/** 提交表单 */

const submitForm = 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-button

type="info"code>

plain

@click="handleRefresh(formData.id)"code>

v-hasPermi="['dangerous:appointment-commission:enterSiteRefresh']"code>

>

<Icon icon="ep:refresh" class="mr-5px" /> 刷新重置code>

</el-button>

对应的按钮方法如下:

/** 刷新 */

const handleRefresh = 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"code>

:model="formData"code>

:rules="formRules"code>

v-loading="formLoading"code>

label-width="0px"code>

:inline-message="true"code>

>

<el-table :data="formData" class="-mt-10px">code>

<el-table-column label="序号" type="index" width="100" />code>

<el-table-column label="提单号" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.billNumber`" :rules="formRules.billNumber" class="mb-0px!">code>

<el-input v-model="row.billNumber" :disabled="formType === 'detail'" placeholder="请输入提单号" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column label="箱号" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.boxNumber`" :rules="formRules.boxNumber" class="mb-0px!">code>

<el-input v-model="row.boxNumber" :disabled="formType === 'detail'" placeholder="请输入箱号" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column label="尺寸" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.boxSize`" :rules="formRules.boxSize" class="mb-0px!">code>

<el-input v-model="row.boxSize" :disabled="formType === 'detail'" placeholder="请输入尺寸" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column label="箱型" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.boxType`" :rules="formRules.boxType" class="mb-0px!">code>

<el-input v-model="row.boxType" :disabled="formType === 'detail'" placeholder="请输入箱型" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column label="货名" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.productName`" :rules="formRules.productName" class="mb-0px!">code>

<el-input v-model="row.productName" :disabled="formType === 'detail'" placeholder="请输入货名" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column label="危险品等级" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.hazardousLevel`" :rules="formRules.hazardousLevel" class="mb-0px!">code>

<el-input v-model="row.hazardousLevel" :disabled="formType === 'detail'" placeholder="请输入危险品等级" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column label="危规号" min-width="150">code>

<template #default="{ row, $index }">code>

<el-form-item :prop="`${$index}.hazardCode`" :rules="formRules.hazardCode" class="mb-0px!">code>

<el-input v-model="row.hazardCode" :disabled="formType === 'detail'" placeholder="请输入危规号" />code>

</el-form-item>

</template>

</el-table-column>

<el-table-column align="center" fixed="right" label="操作" width="60" v-if="formType !== 'detail'">code>

<template #default="{ $index }">code>

<el-button @click="handleDelete($index)" link type="primary" v-if="formType !== 'detail'">删除</el-button>code>

</template>

</el-table-column>

</el-table>

</el-form>

<el-row justify="center" class="mt-3">code>

<el-button @click="handleAdd" round v-if="formType !== 'detail'">+ 添加危险品</el-button>code>

</el-row>

</template>

<script setup lang="ts">code>

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 }

)

/** 新增按钮操作 */

const handleAdd = () => {

if (props.formType === 'detail') return // 禁用“添加危险品”按钮

const row = {

id: undefined,

billNumber: '',

boxNumber: '',

boxSize: '',

boxType: '',

productName: '',

hazardousLevel: '',

hazardCode: '',

appointmentId: props.appointmentId,

}

formData.value.push(row)

}

/** 删除按钮操作 */

const handleDelete = (index) => {

if (props.formType === 'detail') return // 禁用“删除”按钮

formData.value.splice(index, 1)

}

/** 表单校验 */

const validate = () => {

return formRef.value.validate()

}

/** 表单值 */

const getData = () => {

return formData.value

}

// 设置数据的方法

const setData = (newData) => {

formData.value = newData;

};

defineExpose({ validate, getData, setData })

</script>



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。