基于Vue通用组件定制化无侵入的场景解决

48 阅读3分钟

场景描述

  • 场景1
    • 组件1中使用了通用组件2,通用组件2存在高级搜索区域,点击表示“高级搜索”的base-btn组件会展示该区域,借助v-if="isShow"控制。搜索字段包括起始和结束时间范围,此外搜索区域中的表示“清理数据”的base-btn组件点击后会提交API请求,将搜索区所有字段清空,然后刷新下方数据列表。
      • 通用组件2中的表示“清理数据”的base-btn组件的点击处理是<base-btn @click.native="resetFilter"></base-btn>
    • 但该场景需要保留一个默认的字段,即刷新数据列表增加一个字段来控制显示结果。
  • 场景2
    • 组件1中使用了通用组件2,通用组件2存在高级搜索区域,点击表示“高级搜索”的base-btn组件会展示该区域,借助v-if="isShow"控制。搜索字段包括起始和结束时间范围,此外搜索区域中的表示“确定”的base-btn组件点击后会提交API请求。
      • 通用组件2中的表示“确定”的base-btn组件的点击处理是<base-btn @click.native="refreshData"></base-btn>
    • 但搜索时若输入时间相关,需要同时输入起始时间和结束时间,此外,需要判断起始时间早于结束时间。对此要增加相关检查。
      • 检查通过,则继续执行refreshData业务,否则阻止并弹出具体提示消息。
  • 由于通用组件2已被多个组件使用,不能轻易更改通用组件功能。

技术背景

  • ref
  • 通用组件定制化
  • native和v-on(@)
    • v-on(@)。父组件监听子组件的自定义事件,子组件内部需要通过emit触发该自定义事件。
      • 子组件若没有用emit触发,则父组件无法监听到。
    • v-on(@)的修饰符native。在组件的根元素上直接监听一个原生事件(如click等)
  • 组件实例监听
  • <font style="color:rgba(0, 0, 0, 0.85);">this.$watch</font>
    • 一般侦听器会在宿主组件卸载时自动停止,若主动停止,则$watch会返回一个函数来终止。
  • 事件流原理
    • 事件捕获拦截stopImmediatePropagation,addEventListener。
  • 时间校验

方案解决

  • 场景1方案解决
    • 核心思路
      • 覆盖。通过ref动态重写实例方法。
    • 难点介绍
      • 不修改组件2的代码,无侵入方案。
    • 具体方案
      • 在组件1中获取通用组件2的ref值,在mounted中根据ref将通用组件2的resetFilter方法动态覆盖,加入场景的业务逻辑。
        • resetFilter方法中包裹了核心API,可以直接覆盖
    • 实现结果
      • 实现目标刷新。
      • 该方法不会修改组件2的源码,且仅在当前组件1的作用域内生效。
  • 场景2方案解决
    • 核心思路
      • 组件实例监听+事件捕获拦截+校验。
      • 在确定按钮点击后增加事件拦截,先校验,决定是否继续执行原本逻辑。
    • 难点介绍
      • 不修改通用组件2的代码,无侵入。
      • 搜索区域的确定按钮是根据v-if="isShow"来决定显示。初始时不存在于DOM中。
        • 实例级$watch+immediate:true,通过子组件实例直接监听内部isShow状态。
          • 组件1的mounted钩子注册该监听器。
          • 获取该确定按钮的DOM节点,在nextTick中使用。
          • 需要注意组件销毁时移除监听器,避免内存泄漏。
      • click.native是Vue为自定义组件绑定原生DOM事件,但事件监听器被Vue内部管理,不直接暴露,无法直接覆盖。
        • 基于浏览器事件流特性,click.native绑定事件默认在冒泡阶段执行,采用事件捕获阶段拦截,绕开对“获取原事件监听器”的依赖。
        • addEventListener第三个参数设置为true,添加校验事件,在捕获阶段执行,校验失败则e.stopImmediatePropagation阻止后续所有事件。
          • 需要注意组件销毁时移除事件监听器,避免内存泄漏。
      • 确定按钮的组件没有class、id等方便直接定位的,且存在其他base-btn按钮组件。只有样式能区分。
        • 借助querySelector来获取该按钮组件的DOM元素。
      • 不同于场景1,refreshData是核心代码,直接写入了模板中,无法通过动态重写实例方法来解决。
    • 具体方案。组件1中修改。
      • data中添加数据成员。isShowWatcher和targetBtnDom,方便销毁,避免内存泄漏。
      • mounted中调用setupIsShowWatcher方法。
        • setupIsShowWatcher方法中通过this.refs.tableListRef.refs.tableListRef.watch直接添加对通用组件2内部的isShow进行监听,触发后在nextTick中调用checkTargetBtn获取目标DOM元素,并调用方法interceptBtnClick来拦截。interceptBtnClick拦截方法中为该按钮的DOM节点绑定捕获阶段的点击事件,用来校验。
        • 校验逻辑validateTimeRange。
      • beforeDestroy中销毁两个监听器。
    • 实现结果
      • 实现目标校验。
      • 该方法不会修改组件2的源码,且仅在当前组件1的作用域内生效。
  • 注意
    • 不能在created钩子中覆盖,此时还未创建完成,获取获取reset方法,需要在mounted。

参考资料