props / $emit

父组件可以通过 props 的方式给子组件传递数据,而子组件可以通过 $emit 的方式向父组件通信。

父传子 - props

props 有两种常见的写法:

  1. 字符串数组:数组中的字符串就是 attribute 的名称;
  2. 对象类型:对象类型可以在指定 attribute 名称的同时,还可以指定它传递的数据类型、是否是必须的以及默认值等等;

字符串数组

1
2
3
4
5
6
7
8
9
10
11
<div id="app">
<cpn :cgame="games" :cmsg="msg"></cpn>
</div>
<template id="cpn">
<div>
<ul>
<li v-for="item in cgame">{{item}}</li>
</ul>
<div>{{cmsg}}</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
var app = new Vue({
el: '#app',
data: {
msg: '父传子',
games: ['tlbb', 'yxlm', 'cyhx']
},
components: {
cpn: {
template: '#cpn',
props: ['cgame', 'cmsg']
}
}
})

除此之外,如果父组件接收是一个对象,还有如下简便写法:
父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<hello-world v-bind="messageObj"></hello-world>
</div>
</template>

<script>
import HelloWorld from "./components/HelloWorld.vue";
export default {
components: { HelloWorld },
data() {
return {
messageObj: {
title: "标题",
content: "内容",
},
};
},
};
</script>

子组件:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div>
<h2>{{title}}</h2>
<h3>{{content}}</h3>
</div>
</template>

<script>
export default {
props: ["title", "content"],
};
</script>

该写法前提是参数要一一对应,这种就类似于解构写法。


对象类型

当使用对象类型时,可以对传入的数据有更多的限制:

  1. 指定传入的数据类型;
  2. 指定数据是否必传;
  3. 指定数据的默认值;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
export default {
props: {
title: {
type: String,
required: true,
default: "默认值",
},
content: {
type: String,
},
},
};
</script>

注意:type 的类型可以有如下几个:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

对象类型有如下几种写法:

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
<script>
export default {
props: {
// 单个类型
propA: Number,
// 多个类型
propB: [String, Number],
// 必填
propC: {
type: String,
required: true,
},
// 有默认值
propD: {
type: String,
default: "默认值",
},
// 有默认值的对象
propE: {
type: Object,
default() {
return {
message: "默认值对象写法",
};
},
},
// 自定义验证函数
propF: {
validator(value) {
// 值必须匹配字符串中的一个
return ["1", "2", "3"].includes(value);
},
},
// 有默认值的函数
propG: {
type: Function,
default() {
return "函数默认值";
},
},
},
};
</script>

非prop的attribute

当我们传递给组件某个属性时,但该属性并没有定义对应的 props 或者 emits 时,就称之为 非prop的attribute。

常见的包括 class、id、style 等等。

当组件有单个根节点的时候,非 prop 的 attribute 将自动添加到根节点的 attribute 上。

当我们不希望组件的根元素继承 attribute,我们可以在组件中设置 inheritAttrs:false,我们可以通过 $attrs 来访问所有的非props的attribute。

1
2
3
<template>
<div :class="$attrs.class"></div>
</template>

当有多个非props的attribute的时,也可以这样:

1
2
3
<template>
<div v-bind="$attrs"></div>
</template>

当有多个根节点的 attribute 如果没有显式的绑定,那么会报警告,这时需要我们去手动的指定要绑定到哪一个属性上去:

1
2
3
4
5
<template>
<div :class="$attrs.class">1</div>
<div>2</div>
<div>3</div>
</template>

子传父 - $emit

自定义事件:$emit("自定义事件名",自定义事件传递的参数)

1
2
3
4
5
6
7
8
9
<div id="app">
<cpn @fclick="dclick"></cpn>
{{msg}}
</div>
<template id="cpn">
<div>
<div v-for="item in main" @click="itemClick(item)">{{item}}</div>
</div>
</template>
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
var app = new Vue({
el: '#app',
data: {
msg: 'hehe'
},
methods: {
dclick(item) {
console.log(item);
this.msg = item
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
main: [
{ id: 0, name: 'zww' },
{ id: 1, name: 'lq' }
]
}
},
methods: {
itemClick(item) {
this.$emit('fclick', item)
}
},
}
}
})

parent / children

访问子实例 - $children

1
2
3
4
5
6
7
<div id="app">
<cpn></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>子组件</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var app = new Vue({
el: '#app',
methods: {
btnClick() {
this.$children[0].showSon()
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
msg: '子组件'
}
},
methods: {
showSon() {
console.log("hh");
}
},
}
}
})

访问父实例 - $parent

1
2
3
4
5
6
7
8
9
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<span>子组件</span>
<button @click="btnclick">按钮</button>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var app = new Vue({
el: '#app',
data: {
msg: '父组件内容'
},
components: {
cpn: {
template: '#cpn',
methods: {
btnclick() {
console.log(this.$parent.msg);
}
},
}
}
})

ref / refs

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。

1
2
3
4
5
6
7
<div id="app">
<cpn ref="a"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>子组件</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var app = new Vue({
el: '#app',
methods: {
btnClick() {
this.$refs.a.showSon()
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
msg: '子组件'
}
},
methods: {
showSon() {
console.log("hh");
}
},
}
}
})

$root

vm.$root 可以获取当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。

1
2
3
4
5
6
7
8
// app.vue
export default {
data() {
return {
msg: "hello"
};
},
};
1
2
3
4
5
6
// 子组件
export default {
created() {
console.log(this.$root.$children[0].msg); // hello
},
};

因此,子组件可以通过 $root 属性访问父组件实例的属性和方法。


EventBus

EventBus 又称为事件总线。在 Vue 中可以使用 EventBus 来作为沟通的桥梁,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件。

可以通过创建 event-bus.js 文件,也可也在 main.js 文件中初始化 EventBus。

1
2
3
4
5
6
7
// 1. 方法一
import Vue from 'vue';
let eventBus = new Vue();
export default eventBus;

// 2. 方法二
Vue.prototype.$eventBus = new Vue()

现在假设有 A、B 两个组件。

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
// A组件
<template>
<div>
<input
type="text"
v-model="title"
>
<button @click="add">add</button>
</div>
</template>
<script>
import eventBus from "./event-bus";
export default {
name: "A",
data() {
return {
title: "",
};
},
methods: {
add() {
eventBus.$emit("addTitle", this.title);
},
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// B组件
<script>
import eventBus from "./event-bus";
export default {
name: "B",
mounted() {
eventBus.$on("addTitle", this.handleAddTitle);
},
methods: {
handleAddTitle(title) {
console.log(title);
},
},
beforeDestroy() {
eventBus.$off("addTitle", this.handleAddTitle);
},
};
</script>

这样 A、B 两个组件之间就可以通信了。

1
2
3
4
5
6
7
8
9
10
// 发送消息
eventBus.$emit()

// 监听消息
eventBus.$on()

// 移除事件监听者
eventBus.$off('事件名', callback) // 只移除这个回调的监听器
eventBus.$off('事件名') // 移除该事件所有的监听器
eventBus.$off() // 移除所有的事件监听器

provide / inject

它们需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

基本使用:

1
2
3
4
5
6
7
8
// 祖先组件
export default {
provide() {
return {
name: "LqZww",
};
},
};
1
2
3
4
5
6
7
8
// 后代组件
<template>
<div>{{name}}</div>
</template>

export default {
inject: ["name"],
};

provide 和 inject 主要在开发高阶插件 / 组件库时使用。并不推荐用于普通应用程序代码中。


attrs / listeners

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

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

比如,现在有3个嵌套组件,A -> B,B -> C。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// A
<template>
<div>
<B :name="name" :age="age" @add="add"></B>
</div>
</template>
<script>
import B from "./B";
export default {
components: { B },
data() {
return {
name: "zww",
age: 18,
};
},
methods: {
add() {
console.log("add");
},
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
// B
<template>
<div>
<C v-bind="$attrs" v-on="$listeners"></C>
</div>
</template>
<script>
import C from "./C";
export default {
components: { C },
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// C
<template>
<div>
{{name}} - {{age}}
<button @click="myadd">add</button>
</div>
</template>
<script>
export default {
props: ["name", "age"],
methods: {
myadd() {
this.$emit("add");
console.log(this.name);
},
},
};
</script>

Vuex

Vuex官网