Vue.js 父子组件通信的十种方式


Posted in Javascript onOctober 30, 2018

面试官:Vue 中父子组件通信有哪些方式?

自己先想一分钟。

无可否认,现在无论大厂还是小厂都已经用上了Vue.js 框架,简单易上手不说,教程详尽,社区活跃,第三方套件还多。真的是前端开发人员必备技能。而且在面试当中也往往会问到关于 Vue 方面的各种问题,其中大部分面试官会问到如上这种问题。

最近一直在做 Vue项目代码层面上的优化,说实话,优化别人的代码真是件痛苦的事情,功能实现尚且不说,就说代码规范我就能再写出一篇文章来。真的是无规范不成方圆,规范这个东西太重要了!有点扯了,回到主题,咳咳,那就谈谈我对上面的面试题的理解吧,文笔有限,不妥之处,欢迎在文章结尾留言斧正啊,正啊,啊!

概述

几种通信方式无外乎以下几种:

  • Prop (常用)
  • $emit (组件封装用的较多)
  • .sync 语法糖 (较少)
  • $attrs 和 $listeners (组件封装用的较多)
  • provide 和 inject (高阶组件/组件库用的较多)
  • 其他方式通信

详述

下面逐个介绍,大神请绕行。

1. Prop

英式发音:[prɒp]。这个在我们日常开发当中用到的非常多。简单来说, 我们可以通过Prop 向子组件传递数据 。用一个形象的比喻来说,父子组件之间的数据传递相当于自上而下的下水管子,只能从上往下流,不能逆流。这也正是 Vue 的设计理念之单向数据流。而 Prop 正是管道与管道之间的一个衔接口,这样水(数据)才能往下流。说这么多,看代码:

<div id="app">
 <child :content="message"></child>
</div>
// Js
let Child = Vue.extend({
 template: '<h2>{{ content }}</h2>',
 props: {
  content: {
   type: String,
   default: () => { return 'from child' }
  }
 }
})
new Vue({
 el: '#app',
 data: {
  message: 'from parent'
 },
 components: {
  Child
 }
})

你可以狠狠的戳这里查看Demo!浏览器输出:

from parent

2. $emit

英式发音:[iˈmɪt]。官方说法是 触发当前实例上的事件。附加参数都会传给监听器回调 。按照我的理解不知道能不能给大家说明白,先简单看下代码吧:

<div id="app">
 <my-button @greet="sayHi"></my-button>
</div>

let MyButton = Vue.extend({
 template: '<button @click="triggerClick">click</button>',
 data () {
  return {
   greeting: 'vue.js!'
  }
 },
 methods: {
  triggerClick () {
   this.$emit('greet', this.greeting)
  }
 }
})

new Vue({
 el: '#app',
 components: {
  MyButton
 },
 methods: {
  sayHi (val) {
   alert('Hi, ' + val) // 'Hi, vue.js!'
  }
 }
})

你可以狠狠的戳这里查看Demo! 大致逻辑是酱婶儿的:当我在页面上点击按钮时,触发了组件 MyButton 上的监听事件 greet ,并且把参数传给了回调函数 sayHi 。说白了,当我们从子组件Emit(派发) 一个事件之前,其内部都提前在事件队列中On(监听)了这个事件及其监听回调。其实相当于下面这种写法:

vm.$on('greet', function sayHi (val) {
 console.log('Hi, ' + val)
})
vm.$emit('greet', 'vue.js')
// => "Hi, vue.js"

3. .sync 修饰符

这个家伙在 vue@1.x 的时候曾作为双向绑定功能存在,即子组件可以修改父组件中的值。因为它违反了单向数据流的设计理念,所以在 vue@2.0 的时候被干掉了。但是在 vue@2.3.0+ 以上版本又重新引入了这个.sync 修饰符。但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。说白了就是让我们手动进行更新父组件中的值了,从而使数据改动来源更加的明显。下面引入自官方的一段话:

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。

既然作为一个语法糖,肯定是某种写法的简写形式,哪种写法呢,看代码:

<text-document
 v-bind:title="doc.title"
 v-on:update:title="doc.title = $event">
</text-document>

于是我们可以用 .sync 语法糖简写成如下形式:

<text-document v-bind:title.sync="doc.title"></text-document>

废话这么多,如何做到“双向绑定” 呢?让我们进段广告,广告之后更加精彩! ... 好的,欢迎回来。假如我们想实现这样一个效果:改变子组件文本框中的值同时改变父组件中的值。怎么做?列位不妨先想想。先看段代码:

<div id="app">
 <login :name.sync="userName"></login> {{ userName }}
</div>

let Login = Vue.extend({
 template: `
  <div class="input-group">
   <label>姓名:</label>
   <input v-model="text">
  </div>
 `,
 props: ['name'],
 data () {
  return {
   text: ''
  }
 },
 watch: {
  text (newVal) {
   this.$emit('update:name', newVal)
  }
 }
})

new Vue({
 el: '#app',
 data: {
  userName: ''
 },
 components: {
  Login
 }
})

你可以狠狠的戳这里查看Demo!下面划重点,代码里有这一句话:

this.$emit('update:name', newVal)

官方语法是: update:myPropName 其中 myPropName 表示要更新的 prop 值。当然如果你不用 .sync 语法糖使用上面的 .$emit 也能达到同样的效果。仅此而已!

4. $attrs 和 $listeners

官网对 $attrs 的解释如下:

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

官网对 $listeners 的解释如下:

包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

我觉得 $attrs 和 $listeners 属性像两个收纳箱,一个负责收纳属性,一个负责收纳事件,都是以对象的形式来保存数据。看下面的代码解释:

<div id="app">
 <child 
  :foo="foo" 
  :bar="bar"
  @one.native="triggerOne"
  @two="triggerTwo">
 </child>
</div>

从 Html 中可以看到,这里有俩属性和俩方法,区别是属性一个是 prop 声明,事件一个是 .native 修饰器。

let Child = Vue.extend({
 template: '<h2>{{ foo }}</h2>',
 props: ['foo'],
 created () {
  console.log(this.$attrs, this.$listeners)
  // -> {bar: "parent bar"}
  // -> {two: fn}
  
  // 这里我们访问父组件中的 `triggerTwo` 方法
  this.$listeners.two()
  // -> 'two'
 }
})

new Vue({
 el: '#app',
 data: {
  foo: 'parent foo',
  bar: 'parent bar'
 },
 components: {
  Child
 },
 methods: {
  triggerOne () {
   alert('one')
  },
  triggerTwo () {
   alert('two')
  }
 }
})

你可以狠狠的戳这里查看Demo! 可以看到,我们可以通过 $attrs 和 $listeners 进行数据传递,在需要的地方进行调用和处理,还是很方便的。当然,我们还可以通过 v-on="$listeners" 一级级的往下传递,子子孙孙无穷尽也!

一个插曲!

当我们在组件上赋予了一个非Prop 声明时,编译之后的代码会把这些个属性都当成原始属性对待,添加到 html 原生标签上,看上面的代码编译之后的样子:

<h2 bar="parent bar">parent foo</h2>

这样会很难看,同时也爆了某些东西。如何去掉?这正是inheritAttrs 属性的用武之地!给组件加上这个属性就行了,一般是配合 $attrs 使用。看代码:

// 源码
let Child = Vue.extend({
 ...
 inheritAttrs: false, // 默认是 true
 ...
})

再次编译:

<h2>parent foo</h2>

5. provide / inject

他俩是对CP, 感觉挺神秘的。来看下官方对provide / inject 的描述:

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。并且这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

看完描述有点懵懵懂懂!一句话总结就是:小时候你老爸什么东西都先帮你存着等你长大该娶媳妇儿了你要房子给你买要车给你买只要他有的尽量都会满足你。下面是这句话的代码解释:

<div id="app">
 <son></son>
</div>

let Son = Vue.extend({
 template: '<h2>son</h2>',
 inject: {
  house: {
   default: '没房'
  },
  car: {
   default: '没车'
  },
  money: {
   // 长大工作了虽然有点钱
   // 仅供生活费,需要向父母要
   default: '¥4500'
  }
 },
 created () {
  console.log(this.house, this.car, this.money)
  // -> '房子', '车子', '¥10000'
 }
})

new Vue({
 el: '#app',
 provide: {
  house: '房子',
  car: '车子',
  money: '¥10000'
 },
 components: {
  Son
 }
})

你可以狠狠的戳这里查看Demo!

6. 其他方式通信

除了以上五种方式外,其实还有:

EventBus

思路就是声明一个全局Vue实例变量 EventBus , 把所有的通信数据,事件监听都存储到这个变量上。这样就达到在组件间数据共享了,有点类似于Vuex。但这种方式只适用于极小的项目,复杂项目还是推荐 Vuex。下面是实现 EventBus 的简单代码:

<div id="app">
 <child></child>
</div>

// 全局变量
let EventBus = new Vue()

// 子组件
let Child = Vue.extend({
 template: '<h2>child</h2>',
 created () {
  console.log(EventBus.message)
  // -> 'hello'
  EventBus.$emit('received', 'from child')
 }
})

new Vue({
 el: '#app',
 components: {
  Child
 },
 created () {
  // 变量保存
  EventBus.message = 'hello'
  // 事件监听
  EventBus.$on('received', function (val) {
   console.log('received: '+ val)
   // -> 'received: from child'
  })
 }
})

你可以狠狠的戳这里查看Demo!

Vuex

官方推荐的,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

$parent

父实例,如果当前实例有的话。通过访问父实例也能进行数据之间的交互,但极小情况下会直接修改父组件中的数据。

$root

当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。通过访问根组件也能进行数据之间的交互,但极小情况下会直接修改父组件中的数据。

broadcast / dispatch

他俩是 vue@1.0 中的方法,分别是事件广播 和 事件派发。虽然 vue@2.0 里面删掉了,但可以模拟这两个方法。可以借鉴 Element 实现。有时候还是非常有用的,比如我们在开发树形组件的时候等等。

总结

以上所述是小编给大家介绍的Vue.js 父子组件通信的十种方式,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
jquery 插件 任意位置浮动固定层
Dec 25 Javascript
js获取提交的字符串的字节数
Feb 09 Javascript
jquery 表单进行客户端验证demo
Aug 24 Javascript
js关闭父窗口时关闭子窗口
Apr 01 Javascript
指定区域的图片自动按比例缩小的js代码(防止页面被图片撑破)
Feb 21 Javascript
jQuery仿天猫实现超炫的加入购物车
May 04 Javascript
3个可以改善用户体验的AngularJS指令介绍
Jun 18 Javascript
JavaScript实现的多个图片广告交替显示效果代码
Sep 04 Javascript
Angular 4依赖注入学习教程之组件服务注入(二)
Jun 04 Javascript
easyui datagrid 表格中操作栏 按钮图标不显示的解决方法
Jul 27 Javascript
微信小程序wx.navigateTo方法里的events参数使用详情及场景
Jan 07 Javascript
JS实现点击掉落特效
Jan 29 Javascript
vue通过style或者class改变样式的实例代码
Oct 30 #Javascript
vue通过cookie获取用户登录信息的思路详解
Oct 30 #Javascript
微信小程序实现左滑修改、删除功能
Oct 19 #Javascript
小程序实现列表删除功能
Oct 30 #Javascript
require.js 加载过程与使用方法介绍
Oct 30 #Javascript
小程序实现左滑删除功能
Oct 30 #Javascript
提升node.js中使用redis的性能遇到的问题及解决方法
Oct 30 #Javascript
You might like
PHP 5.3.1 安装包 VC9 VC6不同版本的区别是什么
2010/07/04 PHP
php正则表达式学习笔记
2015/11/13 PHP
PHP CodeIgniter分页实例及多条件查询解决方案(推荐)
2017/05/20 PHP
javaScript同意等待代码实现心得
2011/01/01 Javascript
简单的Jquery遮罩层代码实例
2013/11/14 Javascript
按下Enter焦点移至下一个控件的实现js代码
2013/12/11 Javascript
JS的get和set使用示例
2014/02/20 Javascript
js文件Cookie存取值示例代码
2014/02/20 Javascript
jQuery实现炫酷的鼠标轨迹特效
2015/02/01 Javascript
IE6-IE9使用JSON、table.innerHTML所引发的问题
2015/12/22 Javascript
AngularJs 60分钟入门基础教程
2016/04/03 Javascript
js模糊查询实例分享
2016/12/26 Javascript
bootstrap表格分页实例讲解
2016/12/30 Javascript
webpack开发跨域问题解决办法
2017/08/03 Javascript
一种angular的方法级的缓存注解(装饰器)
2018/03/13 Javascript
详解Node.js中path模块的resolve()和join()方法的区别
2018/10/29 Javascript
JS变量提升原理与用法实例浅析
2020/05/22 Javascript
[00:30]塑造者的传承礼包-戴泽“暗影之焰”套装展示视频
2014/04/04 DOTA
[00:12]2018DOTA2亚洲邀请赛 Sccc亮相SOLO赛,今年他又会有什么样的战绩?
2018/04/06 DOTA
[02:51]DOTA2 Supermajor小组分组对阵抽签仪式
2018/06/01 DOTA
详解Python的Django框架中manage命令的使用与扩展
2016/04/11 Python
Python matplotlib 画图窗口显示到gui或者控制台的实例
2018/05/24 Python
python多进程提取处理大量文本的关键词方法
2018/06/05 Python
详解python中的模块及包导入
2019/08/30 Python
python 扩展print打印文件路径和当前时间信息的实例代码
2019/10/11 Python
基于Python和PyYAML读取yaml配置文件数据
2020/01/13 Python
Python API len函数操作过程解析
2020/03/05 Python
python中sys模块是做什么用的
2020/08/16 Python
python 通过exifread读取照片信息
2020/12/24 Python
python 中 .py文件 转 .pyd文件的操作
2021/03/04 Python
物流仓管员岗位职责
2013/12/04 职场文书
《中华少年》教学反思
2014/02/15 职场文书
大学自主招生自荐信范文
2014/02/26 职场文书
小学兴趣小组活动总结
2014/07/07 职场文书
使用kubeadm命令行工具创建kubernetes集群
2022/03/31 Servers
SQL Server中使用表变量和临时表
2022/05/20 SQL Server