奇技淫巧——element-ui组件"无损"拓展自身能力

说明:"无损"指的是无需修改源码。

背景

element-ui相信大家都用过,在一些大的团队还会基于element-ui做二次开发,拓展其能力或者开发一些垂直场景下的业务组件。在使用这类组件库的过程中是否遇到原组件能力不支持,比如缺少prop,event,甚至是一些额外function。笔者在开发过程中就经常遇到此类场景,想要一个属性或方法,在官方文档里找了一遍又一遍,却一无所获。

此时一般会有两种方案:

方案一:copy 组件源码,在项目里创建一个Customxxx,在原有组件的基础上拓展其能力 方案二:去社区里找找有没有平替的组件,npm install下来使用

上面两种方案可以解决问题,但是成本就有点高了。

这次给大家介绍第三种方法,无需修改element-ui的源码,而可以直接拓展现有组件的能力!

PS:本方法属于取巧手段,根据本人经验大约能解决70~80%的问题场景

原理

我们都知道Vue在初始化组件时,会通过new Vue()Vue.extend()创建实例,并将组件选项(如datamethodscomputed等)挂载到该实例上。此时,Vue通过隐式绑定 确保在组件方法中可以调用this拿到当前组件的实例。

并且为了方便开发者操作子组件,还贴心地提供了ref API

本文提到的第三种方案就是借助Vue的这个特性,回归到最原始的方法,去感知组件内部状态的变化,从而拓展组件的能力!

分析思路如下

组件对外能力由属性(prop)+行为(action)体现

=> 拓展现有能力 === 拓展propaction

=> 拓展prop(需修改代码)

=> 拓展action ≈ 增加或拓展 methodevent

=> 增加(需修改代码)

=> 拓展?

如何拓展呢?很简单,方法劫持!并且需要回归使用最朴素的劫持方式,闭包!

为什么不能是 Proxy?

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

因为Vue调用的是原始对象,无法篡改为代理对象...

实践

以项目中实际使用的一个例子为demo给大家演示上述方案。

如下图所示,element-ui的Menu在折叠状态提供了自动弹出SubMenu的能力。

在笔者的场景中需要"感知"弹出的 SubMenu收起动作,查阅了一遍官方文档没有找到类似的event。因此打算拿到 SubMenu的实例,采用劫持实例方法的方式拓展其能力。

找到待劫持的方法

怎么找呢?

没有什么技巧,就是去查阅组件源码。Searching源码...

发现其实SubMenu监听了onmouseleave事件,在该事件回调里完成了submenu popover的销毁。贴上handleMouseleave的代码:

js 复制代码
   handleMouseleave(deepDispatch = false) {
        const {rootMenu} = this;
        if (
          (rootMenu.menuTrigger === 'click' && rootMenu.mode === 'horizontal') ||
          (!rootMenu.collapse && rootMenu.mode === 'vertical')
        ) {
          return;
        }
        this.dispatch('ElSubmenu', 'mouse-leave-child');
        clearTimeout(this.timeout);
        this.timeout = setTimeout(() => {
          !this.mouseInChild && this.rootMenu.closeMenu(this.index);
        }, this.hideTimeout);

        if (this.appendToBody && deepDispatch) {
          if (this.$parent.$options.name === 'ElSubmenu') {
            this.$parent.handleMouseleave(true);
          }
        }
      },

找到了目标方法,那就好办了,下一步便是劫持该方法,添加自己需要的逻辑。

劫持方法,emit事件

js 复制代码
mounted() {
    this.$emit('mounted', true)

    // 劫持 el-submenu 的handleMouseleave方法,以监听 mouse-leave-child 事件
    this.$ref.submenus.forEach(comp => {
      const originFunc = comp.handleMouseleave
      comp.handleMouseleave = (...args) => {
        this.$emit('mouse-leave-child', { index: comp.index, isMenuPopup: comp.isMenuPopup })
        // 重要!!!调用原方法,触发原有逻辑
        originFunc.apply(comp, args)
      }
    })
  }

通过上面的代码,可以成功将mouse-leave-child事件抛出来,方便做进一步的处理。此处略过具体的事件回调方法代码。 怎么样?是不是很简单,hhh

总结

  1. 组件库现有组件难免会出现不满足需求的情况,需要拓展其能力;
  2. 拓展现有组件能力一个方法是全盘copy源码,在源码级别上进行重写、拓展,但缺点是需要连带copy组件的关联文件,产生大量冗余代码;
  3. 本文提供了一种新的拓展组件能力的思路,即用朴素的方法劫持思想,劫持现有组件的方法,对其进行拓展,从而达到无损拓展组件能力的目的。
相关推荐
小皮虾13 分钟前
小程序首页性能优化实战:从 4 秒到 1.8 秒
前端·微信小程序
山河木马16 分钟前
GPU自动处理专题1-裁剪到底在裁什么(裁剪)
前端·webgl·计算机图形学
奔跑的蜗牛ing17 分钟前
CentOS Nginx 安装与静态文件服务器配置指南
前端·面试·架构
子兮曰30 分钟前
Bun 重写为 Rust:11天、64个Claude、16.5万美元,一次改变行业认知的激进实验
前端·后端·bun
子兮曰35 分钟前
Node.js v26.5.0 深度解读:import Text、流式 ReadableStreamTee、以及隐藏的安全加固浪潮
前端·后端·node.js
子兮曰38 分钟前
TypeScript 7.0 RC 深度解读:Go 重写完成,10 倍提速,编译器的「奇点时刻」
前端·后端·typescript
还有多久拿退休金43 分钟前
让飞书知识库跟着 commit 自己长:一套自动化知识库的真实实现
前端·算法·架构
喝拿铁写前端1 小时前
AI 时代,ESLint 应该怎么做得更好
前端·人工智能·代码规范
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二十七):管理员登录验证码开关
前端·后端·ai编程
尼斯湖皮皮怪2 小时前
iceCoder 记忆系统深度分析
前端·javascript