想问一下有没有办法给全局的Button添加自定义指令

ui组件用的iview, 现在有一个自定义指令 v-abc, 想给所有的Button组件都加上这个指令, 有没有办法做到: )

全局注册那个 v-abc 啊

全局注册也需要给每个Button组件添加v-abc才可以, 有没有让Button组件自带v-abc
现在只想到重写组件这一种方法

你是想所有 Button 默认激活 v-abc 的效果?封装一个 ButtonAbc 就行了。

如果觉得单独写一行 import ButtonAbc 太麻烦,也可以写一个代理入口文件,单独把 Button 特殊处理一下,其他全部原样导出。你甚至可以配置 Webpack,让 import iView 直接指向你那个代理文件。

或者,如果 v-abc 的功能逻辑不复杂,注册一个全局钩子检测当前初始化的组件是不是 Button,是的话单独执行 v-abc 逻辑,这样无需对 iView 做什么手脚。

如无特殊需求,建议第一段的方案。

想过封装ButtonAbc, 但是现在是在已经运行的项目中修改, 改动成本比较大
并且第一种还有第二种方案, 应该都不会影响到iview包内部用到的Button组件吧, 是想让内部的Button也能添加上v-abc的效果

你在做埋点?

如果想要无感知地替换所有组件,那只有重新封装 iView 一个办法了。或者第三个方案(全局 mounted 钩子)也可以。

1 Like

是要做按钮防抖节流
重新封装的话担心后续升级iview会遇到坑
全局mounted钩子我去了解一下, 感谢帮助:innocent:

防抖节流啊,那全局 mounted 帮不上忙……

为什么要把防抖节流做到 iView 里面去呢。你可以在业务接口上做。

在业务接口上改成本比较高(都已经做好了)并且会改的代码比较乱, 所以想到能不能从组件层面全局注册directives来改动比较小的解决问题,

一开始是想如果找不到全局注入的办法的话, 大不了重写一个ButtonAbc全局替换一下, 但是有一些iview原生的组件(比如Modal)是只提供了回调接口, Button是封装在Modal组件内部的, 没法通过重写替换成ButtonAbc来解决

还是应该多封装一层, 遇到问题也好解决

所以我更建议在业务接口上做。相比组件,业务接口更集中且完全可掌控,更适合实现这种统一的控制。如果业务接口都实现成了入口函数,加一个 debounce/throttle 也不算很麻烦。

记录一下解决方案
在业务接口上加防抖不太好做, 原因是虽然业务接口都有同一个入口函数, 但是无法判断这个接口是不是点击按钮触发的,即使能做到消耗成本也比较高,随意还是想从组件层面解决问题

一开始尝试的是用extend来重写Button组件,添加防抖指令, 具体的代码是在main.js里添加这段代码

// main.js
Vue.component('Button', Vue.extend({
  template: '<i-button v-throttle v-bind="$attrs" v-on="$listeners"><slot></slot></i-button>'
}))

防抖实现是直接通过操作dom模型终止事件冒泡

但是出现了一个问题,iView里包在Form标签里的Button组件点击时,会触发submit,但是因为route使用的Hash模式,如果submit被终止冒泡就会在路由的#号前面加一个问号,会刷新当前页面,这不是想要的结果

于是又在防抖指令里加了代码阻止默认事件,虽然subumit的问题解决了,但产生了另一个问题,如果是在a标签里用的button标签,就会把a标签的页面跳转也给阻止掉

后来又想了在重写Button的i-button组件外面套一层span,给span添加v-throttle,这样做功能上没有问题,但是vue2里$attrs不带class和style,无法传递到下一层的i-button里面,导致样式全都不对了

直接重写是走不通了,并且想了一下操作dom模型本身也和vue的理念不一致,于是采用了mixin的方式来实现

具体代码就是

// main.js
Vue.component('Button', Vue.options.components.Button.mixin(mixinsButton))

method的mixin策略是直接重写,所以可以直接加一个和Button组件click触发的method同名的方法,给覆盖掉,具体代码

// mixins/button.js
export default {
  data () {
    return { throttleTimer: null }
  },
  methods: {
    _handleClickLink (event) {
      this.$emit('click', event)
      const openInNewWindow = event.ctrlKey || event.metaKey

      this.handleCheckClick(event, openInNewWindow)
    },
    handleClickLink (event) {
      // 添加节流
      if (!this.throttleTimer) {
        this._handleClickLink(event)
        this.throttleTimer = setTimeout(() => {
          clearTimeout(this.throttleTimer)
          this.throttleTimer = null
        }, 1500)
      }
    }
  }
}

这样做也有缺点,就是前面说过如果iview升级了修改了handleClickLink的代码,那这一套代码也要跟着修改,不然实现的逻辑就不一样了

总之,这个问题就这样暂时解决了,不知道还有没有更好的办法

我觉得实际不需要判断是不是点击的 只需要判断是不是同一个接口参数是不是一样的就行