深入浅析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中格式化日期时间型数据函数代码
Nov 08 Javascript
php中给js数组赋值方法
Mar 10 Javascript
jquery解析xml字符串简单示例
Apr 11 Javascript
JS遍历Json字符串中键值对先转成JSON对象再遍历
Aug 15 Javascript
基于jQuery实现拖拽图标到回收站并删除功能
Nov 25 Javascript
jquery控制页面的展开和隐藏实现方法(推荐)
Oct 15 Javascript
js字符串操作总结(必看篇)
Nov 22 Javascript
vue中v-model动态生成的实例详解
Oct 27 Javascript
vue 的keep-alive缓存功能的实现
Mar 22 Javascript
Webpack devServer中的 proxy 实现跨域的解决
Jun 15 Javascript
Vue-不允许嵌套式的渲染方法
Sep 13 Javascript
JavaScript学习笔记之数组基本操作示例
Jan 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版)
2006/10/09 PHP
PHP 图像尺寸调整代码
2010/05/26 PHP
php对文件进行hash运算的方法
2015/04/03 PHP
php仿微信红包分配算法的实现方法
2016/05/13 PHP
PHP实现JS中escape与unescape的方法
2016/07/11 PHP
thinkPHP中_initialize方法实例分析
2016/12/05 PHP
利用php-cli和任务计划实现订单同步功能的方法
2017/05/03 PHP
PHP实现的微信公众号扫码模拟登录功能示例
2019/05/30 PHP
js不完美解决click和dblclick事件冲突问题
2012/07/16 Javascript
深入理解jQuery事件绑定
2016/06/02 Javascript
JS实现的简易拖放效果示例
2016/12/29 Javascript
详解vue 中使用 AJAX获取数据的方法
2017/01/18 Javascript
轻松理解Javascript变量的相关问题
2017/01/20 Javascript
ES6中Iterator与for..of..遍历用法分析
2017/03/31 Javascript
微信小程序 开发MAP(地图)实例详解
2017/06/27 Javascript
详解从新建vue项目到引入组件Element的方法
2017/08/29 Javascript
jQuery实现表单动态添加数据并提交的方法
2018/07/19 jQuery
vue-content-loader内容加载器的使用方法
2018/08/05 Javascript
angularJs使用ng-repeat遍历后选中某一个的方法
2018/09/30 Javascript
JavaScript日期库date-fn.js使用方法解析
2020/09/09 Javascript
Python 3.7新功能之dataclass装饰器详解
2018/04/21 Python
搞定这套Python爬虫面试题(面试会so easy)
2019/04/03 Python
Python中的十大图像处理工具(小结)
2019/06/10 Python
python做反被爬保护的方法
2019/07/01 Python
零基础学python应该从哪里入手
2020/08/11 Python
python实现图片,视频人脸识别(dlib版)
2020/11/18 Python
Forever 21美国官网:美国标志性快时尚品牌
2017/02/20 全球购物
澳大利亚现代波西米亚风格女装网站:Bohemian Traders
2018/04/16 全球购物
MIRTA官网:手工包,100%意大利制造
2020/02/11 全球购物
小学教师管理制度
2014/01/18 职场文书
2015年小学一年级班主任工作总结
2015/05/21 职场文书
2016年小学植树节活动总结
2016/03/16 职场文书
干货:企业内部人才推荐奖励方案!
2019/07/09 职场文书
元素水平垂直居中的方式
2021/03/31 HTML / CSS
选购到合适的激光打印机
2022/04/21 数码科技
MySQL脏读,幻读和不可重复读
2022/05/11 MySQL