父向子传值
:父组件设置v-bind传递数据,子组件设置props 接收数据
子向父传值
: 子组件设置$emit传递数据 ,父组件设置v-on事件绑定接收数据
兄弟组件任意组件通信
:通过事件车的方式传递数据, 新建一个空的全局Vue对象 EventBus,利用$emit发送 , $on接收
不要在子组件中直接修改父组件的状态数据
数据在哪, 更新数据的行为(函数)就应该定义在哪
props
:让组件接收外部传过来的数据,此方式用于父组件向子组件传递数据
props传递数据原则
:单向数据流,只能父传子
注意
:
1.父组件通过传统方式或v-bind动态绑定向子组件传送数据
<!-- App父组件 -->
<template>
<div>
<!-- 传送数据一定要写在父组件的子组件标签上(通过标签属性)-->
<!-- 1.传统方式传送数据 -->
//
<!-- 2.动态绑定传送数据(不限于形式,可能是函数) Student.name会作为表达式自动执行 -->
<Student :name='Student.name'/>
</div>
</template>
<script>
//引入子组件
import Student from "./components/Student.vue";
export default {
name: "App",
components: { Student },
data() {
return {
Student:{
name:'张三'
}
};
},
};
</script>
2.子组件内部通过props接收父组件传递的数据
<!-- Student子组件 -->
//第一种方式(只接收)最常用
props:['name']
//第二种方式(限制类型)
props:{name:String}
//第三种方式(限制类型、限制必要性、指定默认值)
props:{
name:{
type:String, //类型
required:true, //必要性
default:'张三' //默认值
}
}
备注
:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
App.vue 父组件
<template>
<div>
<!-- 传递数据 -->
<!-- :是v-bind动态绑定 18会作为表达式自动执行 -->
<Student name='李四' sex='女' :age='18'></Student>
</div>
</template>
<script>
//引入子组件
import Student from "./components/Student.vue";
export default {
name: "App",
components: { Student },
};
</script>
<style>
</style>
Student.vue 子组件
<template>
<div>
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>学生年龄:{{ myAge + 1 }}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
//若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
myAge: this.age,
};
},
methods: {
updateAge() {
this.myAge++;
},
},
//接收数据 简单声明接收
props:['name','age','sex']
};
</script>
自定义事件
用于子组件向父组件传递数据
使用场景
:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
隔代组件或兄弟组件间通信此种方式不合适
1.绑定自定义事件
方式一
: v-on
<!-- App.vue父组件 -->
// xxx为自定义事件 getStudentName为回调函数(在父组件中)
<Student @xxx="getStudentName" />
...
methods: {
//回调函数
getStudentName(name) {
this.studentName = name;
},
},
方式二:
ref
//通过ref给Student组件打标识
<Student ref="student" />
...
methods: {
//回调函数
getStudentName(name) {
this.studentName = name;
},
},
mounted() {
//通过$refs获取Student组件
//在获取到的Student组件上绑定自定义事件xxx getStudentName为回调函数
this.$refs.student.$on("xxx", this.getStudentName); //$on当...时
},
2.触发自定义事件
方法
:this.$emit(eventName, data)
<!-- Student.vue子组件 -->
<button @click="sendStudentName">把学生名给App</button>
...
methods: {
sendStudentName() {
//触发Student组件实例身上的xxx自定义事事件
this.$emit("xxx", this.name);
}
},
注意
:
案例
App父组件
<template>
<div class="app">
<h1>{{ msg }},学生姓名是:{{ studentName }}</h1>
<!-- 第一种:使用@或v-on -->
<!-- <Student @atguigu="getStudentName" /> -->
<!-- 第二种:使用ref -->
<Student ref="student" @click.native="show" />
</div>
</template>
<script>
import Student from "./components/Student";
export default {
name: "App",
components: { Student },
data() {
return {
msg: "你好啊!",
studentName: "",
};
},
methods: {
getStudentName(name) {
this.studentName = name;
},
show() {
alert(123);
},
},
mounted() {
this.$refs.student.$on("atguigu", this.getStudentName); //绑定自定义事件
},
};
</script>
<style scoped>
.app {
background-color: gray;
padding: 5px;
}
</style>
Student.vue子组件
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>当前求和为:{{ number }}</h2>
<button @click="add">点我number++</button>
<button @click="sendStudentName">把学生名给App</button>
<button @click="unbind">解绑atguigu事件</button>
<button @click="death">销毁当前Student组件的实例(vc)</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
name: "张三",
number: 0,
};
},
methods: {
add() {
this.number++;
},
sendStudentName() {
//触发Student组件实例身上的atguigu事件
this.$emit("atguigu", this.name);
},
unbind() {
this.$off("atguigu"); //解绑一个自定义事件
// this.$off(['atguigu','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件
},
death() {
this.$destroy(); //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
},
},
};
</script>
<style lang="less" scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
全局事件总线
:适用于任意组件间通信。
安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
使用事件总线:
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){…}
}
…
mounted() {
this. b u s . bus. bus.on(‘xxxx’,this.demo)
}
(2)提供数据:this.$bus.$emit('xxxx',数据)
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
Student.vue
<
template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男',
}
},
mounted() {
// console.log('Student',this.x)
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
<style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
mounted() {
// console.log('School',this)
this.$bus.$on('hello',(data)=>{
console.log('我是School组件,收到了数据',data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
5.1 订阅消息
PubSub.subscribe(‘msg’, function(msg, data){})
1
5.2 发布消息
PubSub.publish(‘msg’, data)
1
5.3 示例
订阅消息(绑定事件监听)
import PubSub from ‘pubsub-js’
export default {
mounted () {
// 订阅消息(deleteTodo)
PubSub.subscribe(‘deleteTodo’, (msg, index) => {
this.deleteTodo(index)
})
}
}
1
2
3
4
5
6
7
8
9
10
发布消息(触发事件)
// this.deleteTodo(this.index)
// 发布消息(deleteTodo)
PubSub.publish(‘deleteTodo’, this.index)
1
2
3
5.4 注意
优点: 此方式可实现任意关系组件间通信(数据)
5.5 事件的2 个重要操作
绑定事件监听(订阅消息)
目标: 标签元素
事件名(类型): click/focus
回调函数: function(event){}
触发事件(发布消息)
DOM 事件: 用户在浏览器上对应的界面上做对应的操作
自定义: 编码手动触发
5.6 总结
一种组件间通信的方式,适用于任意组件间通信。
使用步骤:
安装pubsub:npm i pubsub-js
引入: import pubsub from ‘pubsub-js’
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){
demo(data){…}
}
…
mounted() {
this.pid = pubsub.subscribe(‘xxx’,this.demo) //订阅消息
}
1
2
3
4
5
6
7
提供数据:pubsub.publish(‘xxx’,数据)
最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。
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
1、任意组件之间传递数据需要借助于事件车,通过事件车的方式传递数据
2、创建一个Vue的实例,让各个组件共用同一个事件机制。
3、传递数据方,通过一个事件触发eventBus. e m i t ( 方 法 名 , 传 递 的 数 据 ) 。 4 、 接 收 数 据 方 , 通 过 m o u n t e d ( ) 触 发 e v e n t B u s . emit(方法名,传递的数据)。 4、接收数据方,通过mounted(){}触发eventBus. emit(方法名,传递的数据)。4、接收数据方,通过mounted()触发eventBus.on (方法名,function(接收数据的参数){用该组件的数据接收传递过来的数据}),此时函数中的this已经发生了改变,可以使用箭头函数。
首先创建一个单独的js文件bus.js,内容如下
import Vue from 'vue'
export default new Vue
父组件如下:
<template>
<components-a>components-a>
<components-b>components-b>
template>
子组件a如下:
<template>
<div>
<button @click="abtn">A按钮button>
div>
template>
<script>
import from '../../js/bus.js'
export default {
name: 'components-a',
data () {
return {
‘msg':"我是组件A"
}
},
methods:{
abtn:function(){
let _this = this;
//$emit这个方法会触发一个事件
bus .$emit("myFun", _this.msg)
}
}
}
script>
子组件b如下:
<template>
<div>
<div>{{btext}}div>
div>
template>
<script>
import bus from '../../js/bus.js'
export default {
name: 'components-b',
data () {
return {
'btext':"我是B组件内容"
}
},
created:function(){
this.bbtn();
},
methods:{
bbtn:function(){
bus .$on("myFun",(message)=>{
//这里最好用箭头函数,不然this指向有问题
this.btext = message
})
}
}
}
script>
这样的话点击组件A里面的按钮,组件B即可获取A组件传来的值。
匿名插槽 唯一存在
具名插槽 可存在多个
作用域插槽:
插槽的使用:子组件定义插槽的位置,父组件定义插槽的内容, 控制内容的显示与隐藏
编译作用域
:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
插槽的目的
是让我们原来的设备具备更多的扩展性。比如电脑的USB我们可以插入U盘。
组件插槽是Vue的内置组件,为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么。
组件插槽的作用:父组件向子组件传递内容
在子组件中,使用特殊的元素
就可以为子组件开启一个插槽。
该插槽插入什么内容取决于父组件如何使用。
如何封装合适呢?
抽取共性,保留不同。最好的封装方式就是将共性抽取到组件中,将不同预留为插槽。
1.插槽位置
Vue.component('alert-box', {
template: `
Error!
`
})
2.插槽内容
Something had happened.
//12-插槽基本用法.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<alert-box>有bug发生</alert-box>
<alert-box>有一个警告</alert-box>
<alert-box></alert-box>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
Vue.component('alert-box', {
template: `
ERROR:
默认内容
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
1. 插槽定义
<div class="container">
<header>
<slot name="header">slot>
header>
<main>
<slot>slot>
main>
<footer>
<slot name="footer">slot>
footer>
div>
2.插槽内容
//根据名称进行匹配,没有匹配到的放在默认插槽中
<base-layout>
<h1 slot="header">标题内容</h1>
<p>主要内容1</p>
<p>主要内容2</p>
<p slot="footer">底部内容</p>
</base-layout>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<div id="app">
<base-layout>
<template slot='header'>
<p>标题信息1p>
<p>标题信息2p>
template>
<p>主要内容1p>
<p>主要内容2p>
<template slot='footer'>
<p>底部信息信息1p>
<p>底部信息信息2p>
template>
base-layout>
div>
<script type="text/javascript" src="js/vue.js">script>
<script type="text/javascript">
Vue.component('base-layout', {
template: `
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
script>
body>
html>
应用场景
:父组件对子组件的内容进行加工处理
1.插槽定义
<ul>
<li v-for= "item in list" v-bind:key= "item.id" >
<slot v-bind:item="item">
{{item.name}}
</slot>
</li>
</ul>
2.插槽内容
<fruit-list v-bind:list= "list">
<template slot-scope="slotProps">
<strong v-if="slotProps.item.current">
{{ slotProps.item.text }}
</strong>
</template>
</fruit-list>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<style type="text/css">
.current {
color: orange;
}
style>
<body>
<div id="app">
<fruit-list :list='list'>
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}strong>
<span v-else>{{slotProps.info.name}}span>
template>
fruit-list>
div>
<script type="text/javascript" src="js/vue.js">script>
<script type="text/javascript">
Vue.component('fruit-list', {
props: ['list'],
template: `
{{item.name}}
`
});
var vm = new Vue({
el: '#app',
data: {
list: [{
id: 1,
name: 'apple'
},{
id: 2,
name: 'orange'
},{
id: 3,
name: 'banana'
}]
}
});
script>
body>
html>