Vue 组件解耦实践:用回调函数模式替代枚举类型传递

Vue 组件解耦实践:用回调函数模式替代枚举类型传递

前言

在 Vue 组件开发中,父子组件通信是一个常见场景。当子组件需要触发父组件的某个操作,而父组件又需要根据触发来源执行不同逻辑时,很容易写出耦合度较高的代码。本文通过一个真实的登录模块重构案例,介绍如何使用回调函数模式来解耦组件。

问题场景

业务背景

在登录页面中,验证码登录组件有两个操作入口:

  • 点击"获取验证码"按钮

  • 点击"登录"按钮

两个操作都需要检查用户是否同意服务协议。如果未同意,需要弹出协议确认弹窗。用户确认后,根据触发来源执行不同的后续操作。

原有实现

typescript 复制代码
// codeLogin.enum.ts - 子组件定义枚举
export const CodeLoginEnum = {
  CODE_BTN: 'code-btn',    // 获取验证码按钮
  LOGIN_BTN: 'login-btn'   // 登录按钮
} as const;

// codeLogin.vue - 子组件
const getCode = () => {
  if (!isAgree.value) {
    emit('changeCodeAgreeDisplayType', CodeLoginEnum.CODE_BTN);  // 告诉父组件是哪个按钮
    emit('toggleAgreeDialog', true);
    return;
  }
  // ...
}

// login.vue - 父组件
const handleAgreementConfirm = () => {
  if (codeAgreeDisplayType.value === CodeLoginEnum.LOGIN_BTN) {
    // 登录按钮触发的,需要校验验证码
    if (!verifyKey.value) {
      ElMessage.warning('请先获取验证码');
      return;
    }
  }
  codeLoginInstance.value?.doGetCode();
}

问题分析

  1. 父组件依赖子组件内部细节 :父组件需要导入并理解 CodeLoginEnum

  2. 违反开闭原则:子组件新增按钮时,父组件也需要修改

  3. 职责不清:子组件的业务逻辑分散在父子两个组件中

  4. 可测试性差:父组件的逻辑依赖子组件的枚举定义

解决方案:回调函数模式

核心思想

子组件不告诉父组件"我是谁",而是告诉父组件"确认后请通知我"

将"后续要执行的操作"封装为回调函数,保存在子组件内部。父组件只需要在适当时机通知子组件执行即可。

重构后的实现

typescript 复制代码
// codeLogin.vue - 子组件
type PendingCallback = (() => void) | null;
const pendingCallback = ref<PendingCallback>(null);

const getCode = () => {
  if (!isAgree.value) {
    // 保存回调:协议确认后执行获取验证码
    pendingCallback.value = () => {
      executeGetCode();
    };
    emit('toggleAgreeDialog', true);
    return;
  }
  executeGetCode();
}

const codeLogin = () => {
  if (!isAgree.value) {
    // 保存回调:协议确认后执行登录
    pendingCallback.value = () => {
      emit('codeLogin', mobileValue.value, areaCodeValue.value, verifyCodeArg.value);
    };
    emit('toggleAgreeDialog', true);
    return;
  }
  emit('codeLogin', mobileValue.value, areaCodeValue.value, verifyCodeArg.value);
}

// 供父组件调用
const onAgreementConfirmed = () => {
  pendingCallback.value?.();
  pendingCallback.value = null;
}

defineExpose({ onAgreementConfirmed });
typescript 复制代码
// login.vue - 父组件
const handleAgreementConfirm = () => {
  toggleIsAgree(true);
  toggleAgreeDialog(false);

  if (isAccount()) {
    doLoginFn(loginTempData);
  } else {
    // 简单通知子组件执行回调,无需知道具体是什么操作
    codeLoginInstance.value?.onAgreementConfirmed();
  }
}

数据流对比

重构前:

复制代码
┌─────────┐  发送按钮类型   ┌─────────┐  根据类型判断   ┌─────────┐
│ 子组件  │ ─────────────→ │ 父组件  │ ─────────────→ │ 子组件  │
└─────────┘                └─────────┘                └─────────┘

重构后:

scss 复制代码
┌─────────┐  保存回调      ┌─────────┐  通知执行       ┌─────────┐
│ 子组件  │ ─────────────→ │ 父组件  │ ─────────────→ │ 子组件  │
└─────────┘  请求显示弹窗   └─────────┘  onConfirmed   └─────────┘
            (不传类型)                   (不传参数)

方案对比

维度 枚举类型传递 回调函数模式
耦合度 高,父组件依赖子组件枚举 低,父组件只调用方法
扩展性 差,新增类型需改两处 好,只改子组件
职责划分 模糊,逻辑分散 清晰,子组件自治
代码量 需要枚举文件 无额外文件
可测试性 差,依赖外部枚举 好,逻辑内聚

适用场景

回调函数模式适用于以下场景:

  1. 异步确认流程:如本文的协议确认、二次确认弹窗等

  2. 多入口单出口:多个触发点,但后续处理由同一个组件负责

  3. 子组件业务自治:子组件的业务逻辑不应该泄露给父组件

注意事项

  1. 回调清理:执行完回调后记得置空,避免重复执行

  2. 错误处理:回调执行可能失败,需要考虑异常情况

  3. 状态同步 :确保回调执行时,相关状态(如 isAgree)已更新

总结

组件解耦的核心原则是让每个组件只关心自己的职责。当发现父组件需要了解子组件的内部实现细节时,就是重构的信号。

回调函数模式是一种简单有效的解耦手段,它将"做什么"的决策权留给子组件,父组件只负责"何时做"的协调。这种控制反转的思想,在很多设计模式中都有体现,值得在日常开发中灵活运用。

相关推荐
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊9 小时前
jwt介绍
前端
爱敲代码的小鱼9 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax