组件关系分类
- 父子关系
- 非父子关系
父子通信流程
父组件通过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 中变量的修改是无法控制的。换句话说,不知道是哪个组件修改了这个全局状态。