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)已更新

总结

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

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

相关推荐
an86950012 小时前
vue自定义组件this.$emit(“refresh“);
前端·javascript·vue.js
Avicli2 小时前
Gemini3 生成的基于手势控制3D粒子圣诞树
前端·javascript·3d
GinoWi2 小时前
HTML标签 - 列表标签
前端
o__A_A2 小时前
渲染可配置报告模板+自适应宽度(vue3)
前端·vue.js
San302 小时前
拒绝做 DOM 的“搬运工”:从 Vanilla JS 到 Vue 3 响应式思维的进化
javascript·vue.js·响应式编程
JienDa2 小时前
JienDa聊PHP:从Laravel到ThinkPHP的现代Web开发实践
前端·php·laravel
软件技术NINI2 小时前
盒模型在实际项目中有哪些应用场景?
前端·css·html
Beginner x_u2 小时前
从组件点击事件到业务统一入口:一次前端操作链的完整解耦实践
前端·javascript·vue·业务封装
光影少年2 小时前
前端ai开发需要学习哪些东西?
前端·人工智能·学习