资讯专栏INFORMATION COLUMN

关于vue事件监听的一个问题

sixgo / 3156人阅读

摘要:可归根结底,所谓事件监听,通常都是一个需要预处理的过程,即在你初始化你的实例时就需要去为其注册监听。当然既然用了,更好的利用给我们带来的遍历也很重要,所以对于这种很少会出现的麻烦,我们有一个预期,并可以快速定位并修复问题,就可以了

由于新工作需要用vue,所以最近接触最多的也是vue,因为之前一直在用react,所以对于vue上手还是很快的。
我也尽量找一些他们两个的异同点,除了多了一些辅助用的方法以外,最大的不同应该是对于组件间的通信,不仅有props,还有一种事件监听,也是可以通过组件间传递的。
我们知道vue的事件监听是一个很方便的设计,代码上一目了然,而且给我们增加了多种修饰符(虽然我都没怎么用过)来简化你的代码。可归根结底,所谓事件监听,通常都是一个需要预处理的过程,即在你初始化你的实例时就需要去为其注册监听。这当然没什么不好,我们之所以需要做事件监听,就是为了当我们因为业务复杂而形成一个事件需要触发多处回调时,我们可以通过这种注册监听机制更好的管理他们。这一点上他比react做的更好,如果在react里我们需要中途给某个事件添加更多回调,我们需要手动维护更多的代码(这里说的事件监听机制单指vue的v-on方式,react本身dom上的事件是通过事件代理方式统一在document身上并通过每个dom的唯一id来维护一个hashMap来实现的)。
但是,在vue2.+中,vue引入了diff算法和虚拟dom来提升效率。我们知道这些事为了处理频繁更新dom元素所提出的一种优化方案,可频繁变动更新以及事件监听的初始化之间是否会有矛盾,当组件需要变动时,有没有对注册过的事件进行解绑? 我们来写一些简单的代码印证一下。

我们写两个div做的按钮,一个是写的html代码,一个是通过组件的形式插入,两个按钮完全一样,但我们加一个disabled的属性在外层,并通过if-else来判断disabled从而显示不同的按钮(当然正常场景下我们不会这么去写代码,这里只是通过这种方式模拟一种特殊场景,我们自行考虑在我们的业务中是否存在这种场景)。




我们加一点样式,让他尽量好看一点,看着很简单,两个按钮,可点击时为他绑定一个点击事件,不可点击时不为他绑定。不同点是一个是直接写的html代码,一个是组件。组件的代码如下:


然后在mounted周期里加一个1秒的settimeout将disabled变为false,然后我们测试一下
(靠!传图片失败····· 我们用语言描述吧)
总之就是,当disabled还是true得时候,两个按钮点击都会弹出可点击的alert。但当disebled变为false的时候,上面用html写的不会再弹框,可下面用组件写的就还是会弹窗。

这种问题出现时是非常不好定位的,因为代码上很显然不会去调取这个clicktest事件,而在页面上,我们也能确定按钮已经变为不可点击的那一个了。那为什么这个事件还是会被调取呢?

这先要从diff算法说起,传统的diff tree算法的算法复杂度是O(n^3),而react在引入diff算法时,抛除了跨级移动的情况,即只比对同一级的节点异同,让算法复杂度降低到了O(n),让我们可以肆无忌惮(当然也要适可而止)的频繁刷新整个页面。
(呵呵,没图)
diff有一条策略是拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。所以它的比对顺序就是
1)tree diff
2)component diff
3)element diff
回到我们的代码上,我们在进行component diff时,认为他们是相同的组件,然后进行element diff,即进行新增 删除和移动
所以问题就是发生在了这里,在实例化组件的时候我们初始化了事件监听,但在替换相同组件里的dom时,vue并没有对已添加到组件上的事件监听做删除。
我们看一下vue的代码,

Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== "production") {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      for (let i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
  }

vue是通过vdom里的_events属性下确定是否有绑定事件的。我们看一下不可点击的按钮的_events

:
clickTest
:
Array(1)
0
:
ƒ invoker()
length
:
1

发现clicktest还在。这就是问题所在了。

那么我们该如何去回避这样的问题呢,还是应从diff的比对方式来解决问题,还是看代码。

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

也就是对diff来说,所谓相同的第一判定原则是key。
key也是react引入diff时添加的一个属性,用来判断前后vdom树上是否为统一元素(注意是同级关系上),所以我们只需要在代码上加key,就可以避免这个问题


这样,我们在点击按钮时,就不会再出弹框了。
key的作用很广泛,当我们在遍历数组生成dom时,添加一个可确定的唯一id(注意不应该用数组索引),会优化我们的比对效率以及更少的操作dom。我们也会在某个div上添加key以确保他不会因为兄弟元素的变动而被重新渲染(这类div一般会被绑定react或vue意外的事件或动作,如在这个div中生成了一个canvas等)。

那么除了在组件上加这种不必要key值以外,还有别的方法解决吗?

有的,这里有一种很反vue但是类react的方式,就是把回调事件通过props的方式传递,向下面着这样,


        props: {
            "clickTest": {
                type: Function
            }
        },
        methods: {
            handleClick() {
                //this.$emit("clickTest")
                this.clickTest && this.clickTest()
            }
        }

虽然vue给了我们更方便的事件传递的方式,但props里是允许我们去传递任何类型的,我的期望是在真实的dom上或者在公共组件的入口处以外的地方,都是通过props的方式来传递结果的。虽然这种方式很不vue,而且也享受不到v-on给我们带来的遍历,但是这样确实可以减少不必要的麻烦。
当然既然用了vue,更好的利用vue给我们带来的遍历也很重要,所以对于这种很少会出现的麻烦,我们有一个预期,并可以快速定位并修复问题,就可以了

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/90439.html

相关文章

  • 人人都能懂Vue源码系列—09—initEvents

    摘要:回调函数会接收所有传入事件触发函数的额外参数。这种方式类似于中的监听事件和触发事件如果不是上面这种方法指定的钩子函数,就需要执行源码上半部分的代码逻辑。 上篇文章中,我们主要讲了initLiftcycle方法,它的作用是初始化vm实例中和生命周期相关的属性。今天为大家介绍另一个方法——initEvents。从这个方法的名称来看,我们知道它是和事件相关的方法,具体怎么相关,我们先来看源码...

    lovXin 评论0 收藏0
  • Vue.js中v-on(事件处理)

    摘要:不必担心,因为所有的事件处理方法和表达式都严格绑定在当前视图的上,它不会导致任何维护上的困难。当一个被销毁时,所有的事件处理器都会自动被删除。 Vue.js的事件处理 监听事件 我们可以用 v-on 指令监听 DOM 事件来触发一些 JavaScript 代码。 {{msg}} var vm = new Vue({ el:.box, data:{ ...

    YuboonaZhang 评论0 收藏0
  • 关于input一些问题解决方法分享

    摘要:输入框首尾清除空格在中监听键盘事件移动端底部被弹出的键盘遮挡输入框是通过一直放在页面底部,当点击进行输入的时候,就会出现如下图片情况有的机型会遮挡一些。 前言 input是我们接受来自用户的数据常用标签,在前端开发中,相信每个人都会用到这个标签,所以在开发过程中也时候也会遇到一些问题,本文的内容是我在跟input相爱相杀过程中产生的,在此记录分享一下。如果喜欢的话可以点波赞/关注,支持...

    骞讳护 评论0 收藏0
  • 关于input一些问题解决方法分享

    摘要:输入框首尾清除空格在中监听键盘事件移动端底部被弹出的键盘遮挡输入框是通过一直放在页面底部,当点击进行输入的时候,就会出现如下图片情况有的机型会遮挡一些。 前言 input是我们接受来自用户的数据常用标签,在前端开发中,相信每个人都会用到这个标签,所以在开发过程中也时候也会遇到一些问题,本文的内容是我在跟input相爱相杀过程中产生的,在此记录分享一下。如果喜欢的话可以点波赞/关注,支持...

    vibiu 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<