You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否无需传递函数Prop重写Vue可复用按钮组件?

改用事件实现Vue可复用执行按钮组件(避免函数Prop反模式)

问题描述

我想开发一个可复用按钮组件AppExecButton,用来封装执行父组件传入逻辑时的加载状态与错误处理代码。当前实现是通过传递函数作为Prop(这属于Vue反模式),附上现有组件代码和父组件使用示例,请问能不能改用事件实现,同时让父组件不用重复编写try/catch和加载状态相关代码?

原AppExecButton组件代码

<script setup lang="ts">
import { ref } from "vue";
import type { ButtonVariant } from "@/components/types";

const props = defineProps<{
  onClick: () => Promise<unknown> | unknown;
  errorMessage?: string;
  variant?: ButtonVariant;
  disabled?: boolean;
  block?: boolean;
  color?: string;
}>();

const loading = ref(false);

async function handleClick() {
  loading.value = true;
  try {
    await props.onClick();
  } catch (e) {
    const errorMessage = props.errorMessage;
    if (errorMessage) {
      console.error(e);
      throw new Error(errorMessage);
    } else {
      throw e;
    }
  } finally {
    loading.value = false;
  }
}
</script>

<template>
  <v-btn
    @click="handleClick"
    :loading="loading"
    :variant="variant"
    :disabled="disabled"
    :block="block"
    :color="color"
  >
    <slot />
  </v-btn>
</template>

原父组件使用示例

<app-exec-btn
  :on-click="() => downloadNotes()"
  error-message="Failed to download notes"
  block
  variant="outlined"
>
  Download Notes
</app-exec-btn>

解决方案

当然可以改用事件机制实现,这既符合Vue单向数据流的设计原则,又能保留组件封装的加载状态管理和错误处理能力。核心逻辑是让子组件触发自定义事件,父组件监听事件并执行业务逻辑,子组件内部负责处理加载状态和错误捕获。

修改后的AppExecButton组件代码

<script setup lang="ts">
import { ref } from "vue";
import type { ButtonVariant } from "@/components/types";

const props = defineProps<{
  errorMessage?: string;
  variant?: ButtonVariant;
  disabled?: boolean;
  block?: boolean;
  color?: string;
}>();

// 定义自定义事件,父组件将通过该事件绑定业务逻辑
const emit = defineEmits<{
  execute: [];
}>();

const loading = ref(false);

async function handleClick() {
  loading.value = true;
  try {
    // 触发execute事件,等待父组件的业务逻辑执行完成
    await emit("execute");
  } catch (e) {
    const errorMessage = props.errorMessage;
    if (errorMessage) {
      console.error(e);
      throw new Error(errorMessage);
    } else {
      throw e;
    }
  } finally {
    loading.value = false;
  }
}
</script>

<template>
  <v-btn
    @click="handleClick"
    :loading="loading"
    :variant="variant"
    :disabled="disabled"
    :block="block"
    :color="color"
  >
    <slot />
  </v-btn>
</template>

修改后的父组件使用示例

<app-exec-btn
  @execute="downloadNotes"
  error-message="Failed to download notes"
  block
  variant="outlined"
>
  Download Notes
</app-exec-btn>

关键修改说明

  • 移除了原有的onClick Prop,改用execute自定义事件,避免了函数作为Prop的反模式
  • 子组件点击时触发execute事件,父组件通过@execute直接绑定业务函数,无需额外包装箭头函数
  • 子组件依然完整保留了加载状态管理和错误捕获逻辑,父组件只需专注于业务逻辑本身,无需重复编写try/catch和加载状态相关代码

内容的提问来源于stack exchange,提问作者Aniruth N

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 09:33:30