视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
在vue组件中事件如何传递
2020-11-27 19:37:02 责编:小采
文档
 最近的工作需要用到vue,所以最近接触最多的就是vue,下面我给大家介绍下vue组件间事件传递,需要的朋友参考下吧

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

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

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

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

<template>
 <p class="test">
 <p class="btn" v-if="disabled" @click="handleClick">可点击</p>
 <p class="btn" v-else >不可点击</p>
 <Button v-if="disabled" @clickTest="handleClick">可点击</Button>
 <Button v-else>不可点击</Button>
 </p>
</template>

<script>
import Button from './Button'
export default {
 data () {
 return {
 disabled: true
 }
 },
 methods: {
 handleClick() {
 alert('可点击')
 }
 },
 components: {
 Button,
 },
 mounted() {
 setTimeout(() => {
 this.disabled = false
 }, 1000)
 }
}
</script>
<style>
.btn{
 margin: 100px auto;
 width: 200px;
 line-height: 50px;
 border: 1px solid #42b983;
 border-radius: 5px;
 color: #42b983;
}
</style>

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

<template>
 <p class="btn" @click="handleClick"><slot></slot></p>
</template>
<script>
 export default {
 methods: {
 handleClick() {
 this.$emit('clickTest')
 }
 }
 }
</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的代码,

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
:

发现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,就可以避免这个问题

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

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

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

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

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

<Button v-if="disabled" :clickTest="handleClick">可点击</Button>
<Button v-else>不可点击</Button>
 props: {
 'clickTest': {
 type: Function
 }
 },
 methods: {
 handleClick() {
 //this.$emit('clickTest')
 this.clickTest && this.clickTest()
 }
 }

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

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

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

相关文章:

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

使用vue如何实现收藏夹

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

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

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

下载本文
显示全文
专题