我们已经学过的组件通信方式 6 种

props

适用于 父子组件通信

如果父组件给 子组件传递数据(传递的是一个函数),本质是子给父传递数据。
如果父组件给 子组件传递数据(不是函数的情况),本质是父给子传递数据。

书写方式:

1
2
3
4
5
6
['tools']

[type:Array]

{type:Array, default:[]}

自定义事件

组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件

使用场景

A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

绑定自定义事件:

第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

具体代码

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
</div>
</template>

<script>
import Student from './components/Student'

export default {
name:'App',
components:{Student},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
}
}
</script>

<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>

Student.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div class="student">
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template>

<script>
export default {
name:'Student',
data() {
return {
name:'张三',
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
}
},
}
</script>

<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>

第二种方式,在父组件中:

使用 this.$refs.xxx.$on() 这样写起来更灵活,比如可以加定时器啥的。

具体代码

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template>

<script>
import Student from './components/Student'

export default {
name:'App',
components:{Student},
data() {
return {
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
},
mounted() {
this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
},
}
</script>

<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>

Student.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div class="student">
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template>

<script>
export default {
name:'Student',
data() {
return {
name:'张三',
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
}
},
}
</script>

<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>

若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
若想给子组件绑定原生的事件,可以后面加上 native修饰符。
触发自定义事件:this.$emit('atguigu',数据)

使用 this.$emit() 就可以子组件向父组件传数据

解绑自定义事件this.$off('atguigu')

代码

1
2
3
this.$off('atguigu') //解绑一个自定义事件
// this.$off(['atguigu','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件

组件上也可以绑定原生DOM事件,需要使用native修饰符。

代码

1
2
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="show"/>

注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

全局事件总线 $bus

使用场景:万能

Vue.prototype.$bus = this;

pubsub-js 发布与订阅

使用场景:万能

vuex

使用场景:数据非持久化—-万能的

核心概念:5个

state 
mutations
actions 
getters 
modules

插槽

使用场景:父子组件通信

默认插槽
具名插槽
作用域插槽

组件间通信高级

(非常重要, 面试必备)

event深入

  1. 原生DOM–button 可以绑定系统事件,click单击事件等。
  2. 组件标签—可以绑定系统事件,不起作用,因为属于自定义事件了。
    可以加上 .native 变成原生dom事件,其实就是给子组件的上的那个div根标签绑定了事件了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
<div>
<h1>EventTest组件</h1><br/><br/>

<!--
1. 原生DOM--button 可以绑定系统事件,click单击事件等。
2. 组件标签---<Event1>可以绑定系统事件,不起作用,因为属于自定义事件了。
可以加上 .native 变成原生dom事件,其实就是给子组件的上的那个div根标签绑定了事件了。
-->

<!-- 原生DOM事件 -->
<button @click="handler">原生btn按钮</button><br/><br/>

<!-- 使用Event1组件:底下这个组件 @click.native 原生DOM事件,利用事件的委派-->
<!-- 下面组件上 如果 写成@click 就会当成自定义事件了,需要 emit 来触发,这时候点击的时候根本不会执行 -->

<Event1 @click.native="handler1"></Event1><br/>

<hr/>

<!-- 自定义事件对于原生DOM没有任何意义 给原生DOM绑定自定义事件是没有意义的!! -->
<!-- <button @erha="handler3"> 原生的btn</button> -->

<!-- 这里的 @click="handler2" 属于自定义事件,能够触发是因为子组件里面有个button点击后会调用 $emit('click',66666) 这个
后面的 @xxx="handler2" 也是个自定义事件,也是子组件里面有 调用 $emit('xxx',77777)
-->
<Event2 @click="handler2" @xxx="handler2"></Event2><br/><br/>

<!-- 表单元素 color:选取颜色 range:范围条 date:日历 week-->
<input type="week" />
</div>
</template>

<script type="text/ecmascript-6">
import Event1 from './Event1.vue'
import Event2 from './Event2.vue'

export default {
name: 'EventTest',

components: {
Event1,
Event2,
},

methods: {
//原生DOM事件的回调
handler(event){
console.log("原生DOM事件的回调", event);
//控制台就会打印:
// 原生DOM事件的回调 PointerEvent {isTrusted: true, pointerId: 1, width: 1, height: 1, pressure: 0, …}
},
handler1(){
console.log('66666666');
},
handler2(params){
console.log(params);
}
}
}
</script>

Event1.vue

1
2
3
4
5
6
7
<template>
<div style="background: #ccc; height: 80px;">
<h2>Event1组件</h2>
<span>其它内容</span>
</div>
</template>

Event2.vue

1
2
3
4
5
6
7
8
<template>
<div style="background: #ccc; height: 80px;">
<h2>Event2组件</h2>
<button @click="$emit('click',66666)">分发自定义click事件</button><br>
<button @click="$emit('xxx',77777)">分发自定义xxx事件</button><br>
</div>
</template>

v-model深入

实现数据的双向绑定,多用于表单数据的收集。

vue中v-model和:value(即:v-bind:value)

区别:

v-model 实现了视图和data中数据的双向绑定,两者其一改变均改变

v-bind:value 只是将初始化时data中的数据绑定到input上,修改input中的值不会改变data中的数据。

v-model 本质其实是一个语法糖,背后是 v-bind:value 和 v-on:input 2个操作

这里介绍是vue3的组件上的 v-model
https://cn.vuejs.org/guide/components/v-model.html

这里介绍是vue2的组件上的 v-model
https://v2.cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model

一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<div>
<h2>深入v-model</h2>
<input type="text" v-model="msg">
<span>{{msg}}</span>
<br>
<h2>深入v-model原理</h2>

<!-- 原生DOM当中是有oninput事件:当表单元素发生文本的变化的时候就会立即出发 。
这个实现就类似了 v-model 指令了。
下面这个 :value 是什么呢? 原生DOM绑定的动态数据v-bind的缩写。
-->
<input type="text" :value="msg" @input=" msg = $event.target.value"/>
<span>{{msg}}</span>



<!--并非原生DOM:自定义组件, 实现父子组件通信了。
下面的这个 :value 是什么?这是props。
下面的 @input 是什么? 自定义事件。 -->
<CustomInput :value="msg" @input="msg = $event"></CustomInput>
<!-- 上面的写法实现,可以简写下面 -->
<CustomInput v-model="msg"></CustomInput>
<hr>
</div>
</template>

<script type="text/ecmascript-6">
import CustomInput from './CustomInput.vue'
export default {
name: 'ModelTest',
data() {
return {
msg:"我爱塞北的大雪呀"
}
},
components: {
CustomInput
}
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div style="background: #ccc; height: 50px;">
<h2>input包装组件----{{value}}</h2>
<input :value="value" @input="$emit('input',$event.target.value)"/>
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'CustomInput',
props:['value']
}
</script>

属性修饰符sync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<div>
小明的爸爸现在有{{ money }}元

<h2>不使用sync修改符</h2>
<!--
下面这里的 :money="money" 父组件通过props传递 数据给子组件
下面这里的 @update:money 这是个自定义事件,只是这个事件名称比较特殊,有个冒号什么的。
$event 也是不能随便起名的。
-->
<Child :money="money" @update:money="money = $event"></Child>



<h2>使用sync修改符</h2>
<!--
加上sync修饰符,就是上面的一个简写方式。
:money.sync 代表 父组件给子组件传递 props是money, 同时给子组件绑定一个自定义事件 update:money
-->
<Child :money.sync="money"></Child>
<h2>使用v-model修改符</h2>
<hr />
</div>
</template>

<script type="text/ecmascript-6">
import Child from './Child.vue'
import Child2 from './Child2.vue'
export default {
name: 'SyncTest',
data() {
return {
money: 10000
}
},
components: {
Child,
Child2
}
}
</script>

Child.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<div style="background: #ccc; height: 50px;">
<span>小明每次花100元</span>
<button @click="$emit('update:money',money - 100)">花钱</button>
爸爸还剩 {{money}} 元
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'Child',
props:['money']
}
</script>

Child2.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div style="background: #ccc; height: 50px;">
<span>小明每次花100元</span>
<button>花钱</button>
爸爸还剩 ??? 元
</div>
</template>

<script type="text/ecmascript-6">
export default {
name: 'Child2'
}
</script>

$attrs与$listeners

传递静态或动态 Prop

https://v2.cn.vuejs.org/v2/guide/components-props.html#%E4%BC%A0%E9%80%92%E9%9D%99%E6%80%81%E6%88%96%E5%8A%A8%E6%80%81-Prop

像这样,你已经知道了可以像这样给 prop 传入一个静态的值:

1
<blog-post title="My journey with Vue"></blog-post>

你也知道 prop 可以通过 v-bind 动态赋值,缩写 : 来进行动态绑定的 props:

例如:

1
2
3
4
5
6
7
<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<h2>自定义带Hover提示的按钮</h2>
<!-- 二次封装的HintButton按钮的时候,把人家el-button需要的数据传递过去 -->
<HintButton type="success" icon="el-icon-plus" title="我是中国人" @click="handler"/>
</div>
</template>

<script type="text/ecmascript-6">
import HintButton from './HintButton';
export default {
name: 'AttrsListenersTest',
components:{
HintButton
},
methods: {
handler() {
alert('弹弹弹');
},
},
}
</script>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div>
<a :title="title">
<!--
这里使用 v-bind="$attrs" (这里不能用简写方式)接收父组件的所有props,除了 props:['title'] 这里提到的。
不这么做的话:如果props很多 就需要一个一个的写上了:size="$attrs.size"
-->
<el-button :size="$attrs.size" :icon="$attrs.icon" :type="$attrs.type" >添加</el-button>

<!-- v-on 不能简写。 -->
<el-button v-bind="$attrs" v-on="$listeners">添加</el-button>
</a>
</div>
</template>

<script>
export default {
name: "",

props:['title'],
mounted(){
//this.$attrs:可以获取到父亲传递的数据【props】
//this.$attrs是可以获取父亲传递的props数据,如果子组件通过
//props:[],接受,this.$attrs属性是获取不到的
console.log(this.$attrs);

//组件实例上的属性。 可以获取父组件给子组件传递的自定义事件。
console.log(this.$listeners);
}
};
</script>

<style scoped></style>

$children 与 $parent

ref 可以获取到某一个子组件
$children 组件实例属性,可以获取当前组件的全部子组件【数组】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<template>
<div>
<h2>BABA有存款: {{ money }}</h2>
<button @click="JieQianFromXM(100)">找小明借钱100</button><br />
<button @click="JieQianFromXH(150)">找小红借钱150</button><br />
<button @click="JieQianAll(200)">找所有孩子借钱200</button><br />
<button @click="SendInfo">我是baba</button>

<br />

<!-- 小明 可以通过ref 获取子组件,然后操作子组件。 -->
<Son ref="xm" />
<br />
<!-- 小红 -->
<Daughter ref="xh"/>
</div>
</template>

<script>
import Son from "./Son";
import Daughter from "./Daughter";

export default {
name: "ChildrenParentTest",
data() {
return {
money: 1000,
};
},

methods: {
//找儿子借钱
JieQianFromXM(money) {
//父组件的数据累加100
this.money += money;

// 通过ref 拿到了子组件,可以操作子组件的数据和方法了
this.$refs.xm.money -= money;
},
JieQianFromXH(money) {
//父组件的数据累加150
this.money += money;
this.$refs.xh.money -= money;
},
JieQianAll(money){
this.money += 2*money;

// 这里的 $children 可以获取到所有的 子组件!!!
this.$children.forEach(item=>item.money-=money);

//不建议用枚举获取子组件:因为没办法确定到底是那个子组件
// this.$children[0].money -=money;

},
SendInfo(){
//在父组件中获取到子组件(数据+方法)
this.$refs.xm.tinghua();
}
},

components: {
Son,
Daughter,
},
};
</script>

<style></style>

Son.vue 子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
<div style="background: #ccc; height: 50px;">
<h3>儿子小明: 有存款: {{money}}</h3>
<!-- 这里给钱,给到父组件 里面,可以通过 $parent 获取父组件。 -->
<button @click="geiQian(50)">给BABA钱: 50</button>
</div>
</template>

<script>
export default {
name'Son',
data () {
return {
money: 30000
}
},

methods: {
tinghua(){
console.log('我是小明,我听爸爸的话');
},
geiQian(money){
this.money-=money;
this.$parent.money+=money;
}
}
}
</script>

Daughter.vue 子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div style="background: #ccc; height: 50px;">
<h3>女儿小红: 有存款: {{money}}</h3>
<button @click="geiQian(100)">给BABA钱: 100</button>
</div>
</template>

<script>
export default {
name'Daughter',
data () {
return {
money: 20000
}
},

methods: {
geiQian(money){
this.money-=money;
this.$parent.money+=money;
}
}
}
</script>

mixin 混入

1
2
3
4
5
6
7
8
9
10
11
export default {
// 对外暴露的对象,可以放置组件重复的js业务逻辑
methods: {
// 2个子组件这个方法是重复,可以单独提取出来
geiQian(money) {
this.money -= money;
this.$parent.money += money;
}
}

}

引入 mixin 之后 Daughter.vue 子组件 中的 methods 重复的函数就可以省略不写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div style="background: #ccc; height: 50px;">
<h3>女儿小红: 有存款: {{ money }}</h3>
<button @click="geiQian(100)">给BABA钱: 100</button>
</div>
</template>

<script>
import MyMixin from './mixin'

export default {
name: 'Daughter',

data() {
return {
money: 20000
}
},

mixins:[MyMixin],

methods: {}
}
</script>

作用域插槽scope-slot

父子组件通信的,主要通信的是结构。 结构在父组件中定义,可以让子组件 显示不同的结构,
例如是 a标签展示,span 标签展示等等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div>
<h2>效果一: 显示TODO列表时, 已完成的TODO为绿色</h2>
<!-- 子组件,数据来自父组件。。。 -->
<List :todos="todos">
<!-- 书写template, 子组件决定不了结构和外观 ,
这里你是用h标签展示,还是用span标签展示,都由父组件这里决定了 -->
<template slot-scope="todo">
<h5 :style="{color:todo.todo.isComplete?'green':'black'}">{{todo.todo.text}}</h5>
</template>
</List>
<List :todos="todos">
<!-- 书写template -->
<template slot-scope="todo">
<a :style="{color:todo.todo.isComplete?'green':'black'}">{{todo.todo.text}}</a>
</template>
</List>
<hr>
<h2>效果二: 显示TODO列表时, 带序号, TODO的颜色为蓝绿搭配</h2>
<List1 :data="todos">
<template slot-scope="{row,index}">
<h1 :style="{color:row.isComplete?'green':'hotpink'}">索引值{{index}}---------{{row.text}}</h1>
</template>
</List1>
</div>
</template>

<script type="text/ecmascript-6">
//子组件
import List from './List'
import List1 from './List1'
export default {
name: 'ScopeSlotTest',
data () {
return {
todos: [
{id: 1, text: 'AAA', isComplete: false},
{id: 2, text: 'BBB', isComplete: true},
{id: 3, text: 'CCC', isComplete: false},
{id: 4, text: 'DDD', isComplete: false},
]
}
},

components: {
List,
List1
}
}
</script>

List.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<ul>
<li v-for="(todo,index) in todos" :key="index">
<!-- 坑:熊孩子挖到坑,父亲填坑 -->
<!-- 数据来源于父亲:但是子组件决定不了结构与外关,这里还需 :todo="todo" 数据回传给父组件
那个slot的地方。slot-scope="todo"这样来接收数据 -->
<slot :todo="todo"></slot>
</li>
</ul>
</template>

<script>
export default {
name: 'List',
props: {
todos: Array
}
}
</script>

List1.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<ul>
<li v-for="(todo,index) in data" :key="index">
<!-- 坑:熊孩子挖到坑,父亲填坑 -->
<!-- 数据来源于父亲:但是子组件决定不了结构与外观, 这里回传了2个数据 :row="todo" :index="index "。
这样来接收 slot-scope="{todo,index}" 数据(当回传多个数据时候,
这里是一个对象,在接收的地方 使用 解构赋值,key 必须对应一致) -->
<slot :row="todo" :index="index "></slot>
</li>
</ul>
</template>

<script>
export default {
name: 'List1',
props: {
data: Array
}
}
</script>