组件关系分类
- 父子关系
- 非父子关系
父子通信流程
父组件通过props将数据传递给子组件
- 给子组件以添加属性的方式传值
- 子组件内部通过 props 接收
- 模板中直接使用 props 接收的值
父组件 Parent.vue
<template>
<div class="parent" style="border: 3px solid #f60202;text-align: center">
<span>我是 父 组件</span>
<!-- 1.给组件标签,添加属性方式 赋值 -->
<div style="text-align: center">
<Son :title="sonTitle"></Son>
</div>
</div>
</template>
<script>
import Son from '@/pages/test/Son.vue'
export default {
name: 'Parent',
data() {
return {
sonTitle: '父组件传给子组件',
}
},
components: {
Son,
},
}
</script>
<style scoped>
.parent {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
height: 300px;
width: 700px;
}
</style>
子组件 Son.vue
<template>
<div class="son" style="border:3px solid #000">
<!-- 3.直接使用 props 的值 -->
我是 子 组件-- {{title}}
</div>
</template>
<script>
export default {
name: 'Son',
// 2.通过 props 来接收
props: {
title: {
type: String,
require: true
}
}
}
</script>
<style scoped>
.son {
height: 200px;
width: 300px;
margin-left: calc((700px - 300px)/2);
margin-top: calc((300px - 200px)/2);
float: left;
}
</style>
子组件利用$emit通知父组件修改更新
- $emit 触发事件,给父组件发送消息通知
- 父组件监听$emit 触发的事件
- 提供处理函数,在函数的性参中获取传过来的参数
代码省略样式
子组件 Son.vue
<template>
<div class="son" style="border:3px solid #000">
<!-- 3.直接使用 props 的值 -->
我是 子 组件-- {{title}}<br/>
<button @click="changeFu">修改title</button>
</div>
</template>
<script>
export default {
name: 'Son',
methods: {
changeFu() {
// 通过this.$emit向父组件发送通知 名字要保持一致 参数一事件,参数二:参数
this.$emit('changeTitle','newtitle')
}
},
// 通过 props 来接收
props: {
title: {
type: String,
require: true
}
}
}
</script>
父组件 Parent.vue
<template>
<div class="parent" style="border: 3px solid #f60202;text-align: center">
<span>我是 父 组件</span>
<!-- 1.给组件标签,添加属性方式 赋值 -->
<div style="text-align: center">
<Son :title="sonTitle" @changeTitle="handleChange"></Son>
</div>
</div>
</template>
<script>
import Son from '@/pages/test/Son.vue'
export default {
name: 'Parent',
data() {
return {
sonTitle: '父组件传给子组件',
}
},
methods: {
// 提供处理函数,提供逻辑
handleChange(newTitle) {
this.sonTitle = newTitle
}
},
components: {
Son,
},
}
</script>
props 示例
<script>
export default {
// 完整写法(类型、默认值、非空、自定义校验)
props: {
w: {
type: Number,
required: true,
default: 0,
validator(val) {
// console.log(val)
if (val >= 100 || val <= 0) {
console.error('传入的范围必须是 0-100 之间')
return false
} else {
return true
}
},
},
},
}
</script>
- default 和 required 一般不同时写(因为当时必填项时,肯定是有值的)
- default后面如果是简单类型的值,可以直接写默认值。如果是复杂类型的值,则需要以函数的形式 return 一个默认值
props & data、单向数据流
共同点:都可以给组件提供数据。
区别:data 的数据是自己的 → 随便改;prop 的数据是外部的 → 不能直接改,要遵循 单向数据流。
单向数据流:父级 props 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。
非父子通信 — event bus 事件总线
非父子组件之间,进行简易消息传递。(复杂场景→ Vuex)
步骤
- 创建一个都能访问的事件总线 (空 Vue 实例):
import Vue from 'vue'
const Bus = new Vue()
export default Bus
- A 组件(接受方),监听 Bus 的 $on 事件:
created () {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
}
- B 组件(发送方),触发 Bus 的$emit 事件:
Bus.$emit('sendMsg', '这是一个消息')
代码示例
新建 EventBus.js
实例化一个新组件实例并向外暴露,作为兄弟组件传值的媒介:
import Vue from 'vue'
const Bus = new Vue()
export default Bus
新建 BaseA.vue(接收方)
<template>
<div class="base-a">
我是 A 组件(接受方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '@/pages/test/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
this.msg = msg
})
},
}
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
新建 BaseB.vue(发送方)
<template>
<div class="base-b">
<div>我是 B 组件(发布方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from '@/pages/test/EventBus'
export default {
methods: {
sendMsgFn() {
Bus.$emit('sendMsg', '今天天气不错,适合旅游')
},
},
}
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
App.vue
<template>
<div class="app">
<BaseA></BaseA>
<BaseB></BaseB>
</div>
</template>
<script>
import BaseA from "@/pages/test/BaseA.vue";
import BaseB from "@/pages/test/BaseB.vue";
export default {
components: {BaseB, BaseA}
}
</script>
非父子通信 — provide & inject
作用
跨层级共享数据,不只是父子之间,也可以是祖父与孙子之间,曾祖父与重孙之间……
1、父组件 provide 提供数据
export default {
provide () {
return {
// 普通类型【非响应式】
color: this.color,
// 复杂类型【响应式】
userInfo: this.userInfo,
}
}
}
2、子/孙组件 inject 获取数据
export default {
inject: ['color','userInfo'],
created () {
console.log(this.color, this.userInfo)
}
}
传递方式
export default {
data(){
return{
obj:{
name:'JavaScript',
},
developer:'布兰登·艾奇',
year:1995,
update:'2021 年 06 月',
}
},
provide(){
return {
obj: this.obj, // 方式 1.传入一个可监听的对象
developerFn:() => this.developer, // 方式 2.通过 computed 来计算注入的值
year: this.year, // 方式 3.直接传值
app: this, // 方式 4. 提供祖先组件的实例 缺点:实例上挂载很多没有必要的东西 比如:props,methods。
}
}
}
注意
无论点击多少次,孙组件中的诞生于 year 字段永远都是 1995 并不会发生变化,通过 方式 1、方式 2、方式 4 传值是可以响应的。
在孙组件中修改祖组件传递过来的值(方式 1、方式 4),发现对应的祖组件中的值也发生了变化。查看目录爷爷组件一,父组件一,孙组件一
代码示例
爷爷组件 Grandpa.vue
<template>
<div style="border: #fb0707 3px solid ">
<button @click="changeMsg">祖组件触发</button>
<h3>祖组件</h3>
<Parent></Parent>
</div>
</template>
<script>
import Parent from '@/pages/test/Parent.vue';
export default {
data(){
return{
obj:{
name:'JavaScript',
},
developer:'布兰登·艾奇',
year:1995,
update:'2021 年 06 月',
}
},
provide(){
return {
obj: this.obj, // 方式 1.传入一个可监听的对象
developerFn:() => this.developer, // 方式 2.通过 computed 来计算注入的值
year: this.year, // 方式 3.直接传值
app: this, // 方式 4. 提供祖先组件的实例 缺点:实例上挂载很多没有必要的东西 比如:props,methods。
}
},
components: {
Parent,
},
methods:{
changeMsg(){
this.obj.name = 'Vue';
this.developer = '尤雨溪';
this.year = 2014;
this.update = '2021 年 6 月 7 日';
},
},
}
</script>
父组件 Parent.vue
<template>
<div class="wrap" style="border: #0759fb 3px solid ">
<h4>父组件(只做中转)</h4>
<Son/>
</div>
</template>
<script>
import Son from '@/pages/test/Son.vue';
export default {
components:{
Son,
},
}
</script>
孙组件 Son.vue
<template>
<div style="border: #605d5d 3px solid ">
<h5>孙组件</h5>
<span>名称:{{obj.name}}</span> |
<span>作者:{{developer}}</span> |
<span>诞生于:{{year}}</span> |
<span>最后更新于:{{this.app.update}}</span>
</div>
</template>
<script>
export default {
computed:{
developer(){
return this.developerFn()
}
},
inject:['obj','developerFn','year','app'],
}
</script>
爷爷组件一 Grandpa.vue
<template>
<div style="border: #f60202 3px solid ">
<h1>祖组件</h1>
<span>名称:{{obj.name}}</span> |
<span>最后更新于:{{update}}</span>
<parent></parent>
</div>
</template>
<script>
import parent from '@/pages/test/Parent.vue';
export default {
data(){
return{
obj: {
name: 'JavaScript',
},
update: '2021 年 06 月',
}
},
provide() {
return {
obj: this.obj,
app: this,
}
},
components: {
parent,
},
}
</script>
父组件一 Parent.vue
不变
孙组件一 Son.vue
<template>
<div style="border: #656567 3px solid ">
<button @click="changeMsg">孙组件触发</button>
<h3>孙组件</h3>
<span>名称:{{obj.name}}</span> |
<span>最后更新于:{{this.app.update}}</span>
</div>
</template>
<script>
export default {
inject:['obj','app'],
methods: {
changeMsg(){
this.obj.name = 'React';
this.app.update = '2020 年 10 月';
}
},
}
</script>
总结
慎用 provide / inject
Vuex 和 provide/inject 最大的区别:Vuex 中的全局状态的每次修改是可以追踪回溯的,而 provide/inject 中变量的修改是无法控制的。换句话说,不知道是哪个组件修改了这个全局状态。