这篇文章介绍了Vue.js 父子组件之间通信的十种方式,不管是初学者还是已经在用 Vue 的开发者都会有所收获。无可否认,现在无论大厂还是小厂都已经用上了 Vue.js 框架,简单易上手不说,教程详尽,社区活跃,第三方套件还多。真的是前端开发人员必备技能。而且在面试当中也往往会问到关于 Vue 方面的各种问题,其中大部分面试官会问到如上这种问题。

概述

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

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

详述

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

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>

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

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 被识别 (且获取) 的特性绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 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 的描述:

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

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

<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 实现。有时候还是非常有用的,比如我们在开发树形组件的时候等等。

《作者:gongph,链接:https://juejin.im/post/5bd18c72e51d455e3f6e4334》

【Vue课堂】Vue.js 父子组件之间通信的十种方式的更多相关文章

  1. Vue.js 父子组件之间通信的方式

    Vue 父子组件之间的同学有一下几种方式: 1. props 2. $emit -- 组件封装用的比较多 3. .sync -- 语法糖 4. $attrs 和 $listeners -- 组件封装用 ...

  2. 【vue】vue +element 搭建项目,组件之间通信

    父子组件通信 父 通过props属性给 子传递数据 子 操作 父  this.$parent.XXX 子通过$emit传递参数 或者通过vue-bus vue-bus既可以实现父子组件之间的通信,也可 ...

  3. vue项目中的父子组件之间的传值。

    首先说一下父子组件就是在一个vue文件中引入另一个vue文件,被引入vue文件就是子组件,引入vue文件的vue文件就是父组件.而在父组件中是不能直接调用子组件中的变量值的.下面详细说一下,父子组件之 ...

  4. VUE 父子组件之间通信传值 props和 $emit

    1.父组件传值给子组件 $props,子组件传值给父组件 $emit 父组件          <div id="app" >               <tr ...

  5. vue2.0父子组件之间通信

    父组件是通过props属性给子组件通信的来看下代码: 父组件: <parent> <child :child-com="content"></chil ...

  6. Vue父子组件之间通信

    1.父 -> 子.通过props //father.vue <template> <div id="father"> <div><l ...

  7. 【Vue项目笔记】—— 父子组件之间传递参数和子组件执行父组件中的方法

    父组件(MyBlog.vue) <template> <!-- Delete Modal --> <!-- 注意:这里的@deleteBlog中的deleteBlog要和 ...

  8. vue中父子组件之间的传值、非父子组件之间的传值

    在Vue实例中每个组件之间都是相互独立的,都有自己的作用域,所以组件之间是不能直接获取数据.在项目开发中一个组件可能需要获取另一个组件的值,我们可以通过其他方法间接的获取.所以,获取的方法有以下几种: ...

  9. Vue 组件之间通信 All in One

    Vue 组件之间通信 All in One 组件间通信 1. 父子组件之间通信 https://stackblitz.com/edit/vue-parent-child-commutation?fil ...

随机推荐

  1. JS日期选择器

    浏览器自带 浏览器自带日期控件,使用<input type="date">时,点击后会弹出. 1:EDGE 2:火狐 3:谷歌  三种都不一样.略胜于无 练习 模仿火狐 ...

  2. [WC2008]游览计划 解题报告

    [WC2008]游览计划 斯坦纳树板子题,其实就是状压dp 令\(dp_{i,s}\)表示任意点\(i\)联通关键点集合\(s\)的最小代价 然后有转移 \[ dp_{i,S}=\min_{T\in ...

  3. Visible Trees HDU - 2841(容斥)

    对于已经满足条件的(x1,y1),不满足条件的点就是(n*x1,n*y1),所以要求的就是满足点(x,y)的x,y互质,也就是gcd(x,y) == 1,然后就可以用之前多校的方法来做了 另f[i] ...

  4. 课后选做题:MyCP

    目录 CP命令了解 MyCP实现 CP命令了解 作用:cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中.若 ...

  5. A1087. All Roads Lead to Rome

    Indeed there are many different tourist routes from our city to Rome. You are supposed to find your ...

  6. A1106. Lowest Price in Supply Chain

    A supply chain is a network of retailers(零售商), distributors(经销商), and suppliers(供应商)-- everyone invo ...

  7. Pandas库中的DataFrame

    1 简介 DataFrame是Python中Pandas库中的一种数据结构,它类似excel,是一种二维表. 或许说它可能有点像matlab的矩阵,但是matlab的矩阵只能放数值型值(当然matla ...

  8. 第二篇:用Android Studio编写Hello World

    将Android Studio的环境搭建好后,第一个写Hello World测试程序.Android Studio v3.2.1. 一.新建工程 点击Start a new Android Studi ...

  9. java 分隔函数split("",-1)的用途

    转: java 分隔函数split("",-1)的用途 2017年12月14日 11:37:58 jaryle 阅读数:8517   1.如果字符串最后一位有值,则没有区别, 2. ...

  10. python爬虫获取图片

    import re import os import urllib #根据给定的网址来获取网页详细信息,得到的html就是网页的源代码 def getHtml(url): page = urllib. ...