深入浅析vue组件间事件传递


Posted in Javascript onDecember 29, 2017

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

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

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

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

<template>
 <div class="test">
 <div class="btn" v-if="disabled" @click="handleClick">可点击</div>
 <div class="btn" v-else >不可点击</div>
 <Button v-if="disabled" @clickTest="handleClick">可点击</Button>
 <Button v-else>不可点击</Button>
 </div>
</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>
 <div class="btn" @click="handleClick"><slot></slot></div>
</template>
<script>
 export default {
  methods: {
   handleClick() {
    this.$emit('clickTest')
   }
  }
 }
</script>

然后在mounted周期里加一个1秒的settimeout将disabled变为false,然后我们测试一下

深入浅析vue组件间事件传递

深入浅析vue组件间事件传递

当disabled还是true得时候,两个按钮点击都会弹出可点击的alert。但当disebled变为false的时候,上面用html写的不会再弹框,可下面用组件写的就还是会弹窗。

深入浅析vue组件间事件传递

这种问题出现时是非常不好定位的,因为代码上很显然不会去调取这个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。我们也会在某个div上添加key以确保他不会因为兄弟元素的变动而被重新渲染(这类div一般会被绑定react或vue以外的事件或动作,如在这个div中生成了一个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组件间事件传递,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
用js遍历 table的脚本
Jul 23 Javascript
javascript 模拟点击广告
Jan 02 Javascript
jquery 的 $(&quot;#id&quot;).html() 无内容的解决方法
Jun 07 Javascript
JavaScript中的prototype.bind()方法介绍
Apr 04 Javascript
JavaScript中创建对象的7种模式详解
Feb 21 Javascript
基于Vue实现tab栏切换内容不断实时刷新数据功能
Apr 13 Javascript
JS字符串按逗号和回车分隔的方法
Apr 25 Javascript
require.js与bootstrap结合实现简单的页面登录和页面跳转功能
May 12 Javascript
Vue框架中正确引入JS库的方法介绍
Jul 30 Javascript
基于js粘贴事件paste简单解析以及遇到的坑
Sep 07 Javascript
微信小程序实现登录遮罩效果
Nov 01 Javascript
Vue中import from的来源及省略后缀与加载文件夹问题
Feb 09 Javascript
详解Vue 事件修饰符capture 的使用
Dec 29 #Javascript
react-router browserHistory刷新页面404问题解决方法
Dec 29 #Javascript
node简单实现一个更改头像功能的示例
Dec 29 #Javascript
JavaScript 中使用 Generator的方法
Dec 29 #Javascript
js中url对象化管理分析
Dec 29 #Javascript
JS计算距当前时间的时间差实例
Dec 29 #Javascript
JS控制鼠标拒绝点击某一按钮的实例
Dec 29 #Javascript
You might like
PHP中uploaded_files函数使用方法详解
2011/03/09 PHP
php数组函数序列之array_slice() - 在数组中根据条件取出一段值,并返回
2011/11/07 PHP
PHP排序算法之希尔排序(Shell Sort)实例分析
2018/04/20 PHP
php处理多图上传压缩代码功能
2018/06/13 PHP
实现超用户体验 table排序javascript实现代码
2009/06/22 Javascript
js更优雅的兼容
2010/08/12 Javascript
Tab页界面 用jQuery及Ajax技术实现(php后台)
2011/10/12 Javascript
JavaScript高级程序设计阅读笔记(六) ECMAScript中的运算符(二)
2012/02/27 Javascript
浅谈JavaScript函数参数的可修改性问题
2013/12/05 Javascript
基于JS实现导航条之调用网页助手小精灵的方法
2016/06/17 Javascript
Angular2关于@angular/cli默认端口号配置的问题
2017/07/15 Javascript
详解在vue-cli中使用路由
2017/09/25 Javascript
vue 设置proxyTable参数进行代理跨域
2018/04/09 Javascript
JavaScript解决浮点数计算不准确问题的方法分析
2018/07/09 Javascript
JS块级作用域和私有变量实例分析
2019/05/11 Javascript
解决layer弹出层的内容页点击按钮跳转到新的页面问题
2019/09/14 Javascript
原生JavaScript之es6中Class的用法分析
2020/02/23 Javascript
JS实现页面鼠标点击出现图片特效
2020/08/19 Javascript
使用Python的Scrapy框架十分钟爬取美女图
2016/12/26 Python
pandas 获取季度,月度,年度首尾日期的方法
2018/04/11 Python
Python求两个圆的交点坐标或三个圆的交点坐标方法
2018/11/07 Python
pygame实现五子棋游戏
2019/10/29 Python
python 链接sqlserver 写接口实例
2020/03/11 Python
Python如何将函数值赋给变量
2020/04/28 Python
opencv 图像轮廓的实现示例
2020/07/08 Python
Python如何使用27行代码绘制星星图
2020/07/20 Python
Python对excel的基本操作方法
2021/02/18 Python
阿联酋团购网站:Groupon阿联酋
2016/10/14 全球购物
英国独特的时尚和生活方式品牌:JOY
2018/03/17 全球购物
哈曼俄罗斯官方网上商店:Harman.club
2020/07/24 全球购物
入党积极分子自我鉴定范文
2014/03/25 职场文书
基层党员学习党的群众路线教育实践活动心得体会
2014/11/04 职场文书
2015年保育员个人工作总结
2015/05/13 职场文书
2015年节能降耗工作总结
2015/05/22 职场文书
pytorch训练神经网络爆内存的解决方案
2021/05/22 Python
html5表单的required属性使用
2021/07/07 HTML / CSS