当前位置:Gxlcms > JavaScript > 在vue组件中事件如何传递

在vue组件中事件如何传递

时间:2021-07-01 10:21:17 帮助过:167人阅读

最近的工作需要用到vue,所以最近接触最多的就是vue,下面我给大家介绍下vue组件间事件传递,需要的朋友参考下吧

由于新工作需要用vue,所以最近接触最多的也是vue,因为之前一直在用react,所以对于vue上手还是很快的。

我也尽量找一些他们两个的异同点,除了多了一些辅助用的方法以外,最大的不同应该是对于组件间的通信,不仅有props,还有一种事件监听,也是可以通过组件间传递的。

但是,在vue2.+中,vue引入了diff算法和虚拟dom来提升效率。我们知道这些事为了处理频繁更新dom元素所提出的一种优化方案,可频繁变动更新以及事件监听的初始化之间是否会有矛盾,当组件需要变动时,有没有对注册过的事件进行解绑? 我们来写一些简单的代码印证一下。

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

  1. <template>
  2. <p class="test">
  3. <p class="btn" v-if="disabled" @click="handleClick">可点击</p>
  4. <p class="btn" v-else >不可点击</p>
  5. <Button v-if="disabled" @clickTest="handleClick">可点击</Button>
  6. <Button v-else>不可点击</Button>
  7. </p>
  8. </template>
  9. <script>
  10. import Button from './Button'
  11. export default {
  12. data () {
  13. return {
  14. disabled: true
  15. }
  16. },
  17. methods: {
  18. handleClick() {
  19. alert('可点击')
  20. }
  21. },
  22. components: {
  23. Button,
  24. },
  25. mounted() {
  26. setTimeout(() => {
  27. this.disabled = false
  28. }, 1000)
  29. }
  30. }
  31. </script>
  32. <style>
  33. .btn{
  34. margin: 100px auto;
  35. width: 200px;
  36. line-height: 50px;
  37. border: 1px solid #42b983;
  38. border-radius: 5px;
  39. color: #42b983;
  40. }
  41. </style>

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

  1. <template>
  2. <p class="btn" @click="handleClick"><slot></slot></p>
  3. </template>
  4. <script>
  5. export default {
  6. methods: {
  7. handleClick() {
  8. this.$emit('clickTest')
  9. }
  10. }
  11. }
  12. </script>

然后在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的代码,

  1. Vue.prototype.$emit = function (event: string): Component {
  2. const vm: Component = this
  3. if (process.env.NODE_ENV !== 'production') {
  4. const lowerCaseEvent = event.toLowerCase()
  5. if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
  6. tip(
  7. `Event "${lowerCaseEvent}" is emitted in component ` +
  8. `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
  9. `Note that HTML attributes are case-insensitive and you cannot use ` +
  10. `v-on to listen to camelCase events when using in-DOM templates. ` +
  11. `You should probably use "${hyphenate(event)}" instead of "${event}".`
  12. )
  13. }
  14. }
  15. let cbs = vm._events[event]
  16. if (cbs) {
  17. cbs = cbs.length > 1 ? toArray(cbs) : cbs
  18. const args = toArray(arguments, 1)
  19. for (let i = 0, l = cbs.length; i < l; i++) {
  20. try {
  21. cbs[i].apply(vm, args)
  22. } catch (e) {
  23. handleError(e, vm, `event handler for "${event}"`)
  24. }
  25. }
  26. }
  27. return vm
  28. }

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

  1. :
  2. clickTest
  3. :
  4. Array(1)
  5. 0
  6. :
  7. ƒ invoker()
  8. length
  9. :

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

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

  1. function sameVnode (a, b) {
  2. return (
  3. a.key === b.key && (
  4. (
  5. a.tag === b.tag &&
  6. a.isComment === b.isComment &&
  7. isDef(a.data) === isDef(b.data) &&
  8. sameInputType(a, b)
  9. ) || (
  10. isTrue(a.isAsyncPlaceholder) &&
  11. a.asyncFactory === b.asyncFactory &&
  12. isUndef(b.asyncFactory.error)
  13. )
  14. )
  15. )
  16. }

也就是对diff来说,所谓相同的第一判定原则是key。

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

  1. <Button key="1" v-if="disabled" @clickTest="handleClick">可点击</Button>
  2. <Button key="2" v-else>不可点击</Button>

这样,我们在点击按钮时,就不会再出弹框了。

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

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

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

  1. <Button v-if="disabled" :clickTest="handleClick">可点击</Button>
  2. <Button v-else>不可点击</Button>
  3. props: {
  4. 'clickTest': {
  5. type: Function
  6. }
  7. },
  8. methods: {
  9. handleClick() {
  10. //this.$emit('clickTest')
  11. this.clickTest && this.clickTest()
  12. }
  13. }

虽然vue给了我们更方便的事件传递的方式,但props里是允许我们去传递任何类型的,我的期望是在真实的dom上或者在公共组件的入口处以外的地方,都是通过props的方式来传递结果的。虽然这种方式很不vue,而且也享受不到v-on给我们带来的遍历,但是这样确实可以减少不必要的麻烦。

当然既然用了vue,更好的利用vue给我们带来的便利也很重要,所以对于这种很少会出现的麻烦,我们有一个预期,并可以快速定位并修复问题,就可以了。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

在vue中如何通过v-for处理数组

使用vue如何实现收藏夹

在node.js中有关npm和webpack配置方法

如何通过js将当前时间格式化?

使用vue引入css,less相关问题

以上就是在vue组件中事件如何传递的详细内容,更多请关注Gxl网其它相关文章!

人气教程排行