问题背景:
在实际前端开发中,当页面包含多个按钮时,代码往往会出现大量重复的加载状态管理逻辑,不仅会造成代码冗余,而且会导致遗漏状态或异常处理不完善
<template>
<el-button :loading="loading1" @click="onClick1">按钮1</el-button>
<el-button :loading="loading2" @click="onClick2">按钮2</el-button>
<el-button :loading="loading3" @click="onClick3">按钮3</el-button>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// 每个按钮都需要独立的loading状态变量
const loading1 = ref(false)
const loading2 = ref(false)
const loading3 = ref(false)
// 每个点击事件都需要重复的loading状态管理逻辑
async function onClick1() {
loading1.value = true
try {
// 执行具体业务逻辑
await fetchData1()
} finally {
loading1.value = false
}
}
function onClick2() {/**省略*/}
function onClick3() {/**省略*/}
</script>
解决方案:
通过封装一个智能的 MyButton 组件,可以自动处理加载状态,简化代码:
<template>
<!-- 使用方式简化,只需传递点击事件 -->
<my-button @click="onClick1">按钮1</my-button>
<my-button @click="onClick2">
按钮2
<template #loading>提交中...</template>
</my-button>
<my-button @click="onClick3">按钮3</my-button>
</template>
<script setup lang="ts">
// 业务逻辑更纯粹,无需关心loading状态
async function onClick1() {
await fetchData1()
}
function onClick2() {/**省略*/}
function onClick3() {/**省略*/}
</script>
组件实现代码
<template>
<el-button
v-bind="filteredAttrs"
:loading="loading"
@click="handleClick"
>
<!-- 支持自定义加载状态文本 -->
<template v-if="loading && $slots.loading">
<slot name="loading"></slot>
</template>
<!-- 支持自定义按钮内容 -->
<slot></slot>
</el-button>
</template>
<script setup lang="ts">
import { ref, useAttrs, computed } from 'vue'
// !禁用默认的属性继承,避免属性和事件重复绑定到根元素
defineOptions({
inheritAttrs: false
})
// 获取父组件传递的所有属性和事件(除了在props中声明的)
const attrs = useAttrs()
// 计算属性:过滤掉 onClick 事件,避免重复绑定
const filteredAttrs = computed(() => {
const { onClick, ...rest } = attrs
return rest
})
const loading = ref<boolean>(false)
/**
* 处理按钮点击事件
* 自动管理loading状态,并执行父组件传递的onClick方法
*/
async function handleClick() {
loading.value = true
try {
// 执行父组件传递的点击事件处理函数(支持异步操作)
await (attrs.onClick as (() => Promise<void> | void))?.()
} catch (error) {
console.error('按钮操作执行失败:', error)
// 可选:向上抛出错误,让父组件处理
// throw error
} finally {
loading.value = false
}
}
</script>