在模块系统中,组件都是单文件组件,即每一个组件都在不同的组件文件中,所以传递数据需要借助一些工具和方法。传递数据的情况有,父子数据传递,子父数据传递,不同组件间数据传递。
父组件向子组件传递数据
props
prop
用于从父组件向子组件传递数据时自组建的接收器:props:["name","age","sex"]
。- 在父组件传递数据时:
<my-student name="张三" :age="16" sex="男"></my-student>
- 注意age的传参方式,完整写法是
v-bind:age= "18"
,这样动态地向子组件传递数据,子组件接受到的数据就是引号里面的 数字类型的18。 - 如果不使用
v-bind:
的方式传递数据,子组件接收到的数据则都是字符串类型的。 - prop还可以是对象类型的:
props:{ name:[String,Number], age:{ type: Number, default: 10 }, sex:{ type: String, required: true } },
- 注意age的传参方式,完整写法是
案例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Vue</title>
<link rel="icon" href="../logo.svg">
</head>
<body>
<div id="root">
<h1>{{msg}}</h1>
<hr>
<!--使用组件-->
<School></School>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
Vue.config.productionTip=false;
// 创建Student组件
const Student=Vue.extend({
props:["name","age","sex"],//接收数据
template:`
<div>
<p>学生姓名:{{name}}</p>
<p>学生年龄:{{age}}</p>
<p>学生性别:{{sex}}</p>
</div>`
})
// 创建School组件
const School = Vue.extend({
data(){
return{
name:"五道口技术学院",
add:"北京"
}
},
components:{ //在组建内注册局部组件
'my-student':Student
},
template:`
<div>
<p>学校名字:{{name}}</p>
<p>学校地址:{{add}}</p>
<hr>
学生组件:
<!--嵌套组件并传递数据-->
<my-student name="张三" :age="16" sex="男"></my-student>
</div>
`
})
//注册组件(全局注册)
Vue.component("School",School);
var vm = new Vue({
el:"#root",
data:{
msg:"hello Vue!"
}
})
</script>
</body>
</html>
效果:
子组件向父组件传递数据
一、props
把学校名传递给App组件
子组件向父组件传递数据也可以借助props:
- 在父组件通过v-bind 向子组件传递一个函数类型的props,
- 子组件通过props 接受,
- 在适当的事件或者钩子函数内调用传递过来的事件,并传递对应的数据。
代码:
App.vue(本案例通过子组件的按钮点击事件,实现数据的传递给父组件)
<template>
<div id="app">
<h1>{{msg}}</h1>
<School :getSchoolName="getSchoolName"/>
<hr>
<Student/>
</div>
</template>
<script>
import School from './components/School';
import Student from './components/Student';
export default {
name: 'App',
data() {
return { msg:"你好啊!"}
},
components: {School,Student},
methods: {
getSchoolName(name){console.log("接收学校名字:",name) }
},
}
</script>
<style scope>
#app{ background: #ccc;padding:5px 10px;}
</style>
School.vue
<template>
<div class="school">
<p>学校名字:{{name}}</p>
<p>学校地址:{{add}}</p>
<p>办校年限:{{age}}</p>
<button @click = "sendSchoolName">传递学校名给App组件</button>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data() {
return {
name:"五道口技术学院",
add:"北京",
age:100
}
},
methods: {
sendSchoolName(){ this.getSchoolName(this.name); }
},
}
</script>
<style scope>
.school{background: rgb(180, 95, 95); color: #fff;padding: 5px 10px;}
</style>
效果(点击了按钮):
二、自定义事件
把学生名传递给App组件
- 父组件内给子组件定义一个自定义事件
- 定义自定义事件可以在组件上直接定义自定义事件。
- 也可以给子组件添加
ref = student
属性,通过$.refs. student.$on()
定位到组件,给组件添加自定义事件 。
- 在子组件内部在合适的事件或者钩子函数内部,通过
$emit
触发这个自定义事件(本案例通过按钮的点击事件触发)。
App.vue
<template>
<div id="app">
<h1>{{msg}}</h1>
<!--子给父传数据: 通过父组件给子组件传递函数类型的props实现 -->
<School :getSchoolName="getSchoolName"/>
<hr>
<!--子给父传数据: 通过父组件给子组件绑定一个自定义事件实现 -->
<Student @getStudentName = 'getStudentName'/>
<hr>
<!--子给父传数据: 通过父组件利用 ref 给子组件绑定一个自定义事件实现 -->
<Student ref="student"/>
</div>
</template>
<script>
import School from './components/School';
import Student from './components/Student';
export default {
name: 'App',
data() {
return {
msg:"你好啊!"
}
},
components: {School,Student},
methods: {
getSchoolName(name){
console.log("接收学校名字:",name)
},
getStudentName(name){
console.log("接收学生名字:",name)
}
},
}
</script>
<!--...-->
Student.vue
<template>
<div class="student">
<p>学生姓名:{{name}}</p>
<p>学生年龄:{{age}}</p>
<p>学生性别:{{sex}}</p>
<button @click = "sendStudentName">传递学生名给App组件</button>
</div>
</template>
<script>
export default {
name:'Student',
data() {
return {
name:"张三",
age:18,
sex:"男"
}
},
methods: {
sendStudentName(){
this.$emit("getStudentName",this.name);
this.$emit("getStuName",this.name);
}
},
//页面挂在完毕
mounted() {
// 通过ref 为student组件绑定自定义事件
this.$refs.student.$on("getStuName",this.getStudentName);
},
}
</script>
<!--...-->
效果:(依次点击两个按钮后的效果)
注意:第一个和第二个组件的自定义事件页面加载完毕就可以触发,但是第三个自定义事件须要5秒后才可以触发成功。
自定义事件其他问题
- 事件修饰符:自定义事件上也可以添加事件修饰符,默认事件可以添加的,自定义事件都可以绑定自定义事件是通过:
<Student @getStudentName.sync = 'getStudentName'/>
。 - 解绑自定义事件,定义的额自定义事件,最好在使用完以后找个合适的时机解绑掉,类似定时器使用完最好也要清除定时器一样:
vm.$off( [event, callback] ) =>this.$off('getStudentName')
。 - 扩展:组件上的事件都是通过
v-on:/ @
添加的自定义事件,那么要想在组件上使用原生事件,比如click
事件,就需要添加一个后缀.native
:<Student @click.native = 'getStudentName'/>
自定义事件中的this
上例中第三个组件使用的 ref
标记组件,然后在methods
中定义好回调函数,最后在mounted钩子函数内部使用:
this.$refs.student.$on("getStuName",fn)
:因为是student
组件点用的自定义事件,所以fn
回调函数中的this指向触发自定义事件的组件.- 案例中回调函数使用的是
methods
中的定义好的函数,因为methods
中函数都是vue
管理的函数,所以函数中this
都是指向vue
的实例对象vm
。- 解决办法:
this.$refs.student.$on("getStuName",(name)=>{/*...*/})
- 箭头函数没有
this
,所以如果使用this则需要向上一级查找。 mounted
函数是vue管理的函数,this指向vm,所以回调函数内的this
又指向了vm
。
- 箭头函数没有
- 解决办法:
组件间的通讯
还是用上述的案例,需求是把学生姓名传递给学校:
第一个办法可以借助 props
和自定义事件把数据传递给Student
的名字传递父组件App
,再通过父组件传递给School
组件,可以实现,但是比较繁琐。
还可以通过全局事件总线和消息的订阅和发布来实现
全局事件总线
全局事件总线的使用需要三步骤:安装,监听和触发
- 安装全局事件总线:
// main.js import Vue from 'vue' import App from './App.vue'; Vue.config.productionTip = false new Vue({ beforeCreate(){ //安装全局事件总线 Vue.prototype.$bus = this }, render: h => h(App), }).$mount('#app')
- 在需要数据的组件内部定义并监听全局事件总线的一个事件,并在回调函数内部,获取到需要的数据。
School.Vue<template> <div class="school"> <p>学校名字:{{name}}</p> <p>学校地址:{{add}}</p> <p>办校年限:{{age}}</p> <button @click = "sendSchoolName">传递学校名给App组件</button> </div> </template> <script> export default { name:'School', props:['getSchoolName'], data() { return { name:"五道口技术学院", add:"北京", age:100 } }, methods: { sendSchoolName(){} }, mounted() { this.$bus.$on("StudentName",(name)=>{ console.log("School接收到的数据",name); }) }, } </script> <!-- ...-->
- 在提供数据的组件内触发全局事件总线的该事件,并传入数据。
Student.vue<template> <div class="student"> <p>学生姓名:{{name}}</p> <p>学生年龄:{{age}}</p> <p>学生性别:{{sex}}</p> <button @click = "sendStudentName">传递学生名给School组件</button> </div> </template> <script> export default { name:'Student', data() { return { name:"张三", age:18, sex:"男" } }, methods: { sendStudentName(){ this.$bus.$emit("StudentName",this.name); } }, } </script> <!--...-->
- 在App根组件中分别引入两个子组件:
App.vue
<template>
<div id="app">
<h1>{{msg}}</h1>
<School/>
<hr>
<Student/>
</div>
</template>
<script>
import School from './components/School';
import Student from './components/Student';
export default {
name: 'App',
data() {
return {
msg:"你好啊!"
}
},
components: {School,Student},
}
</script>
<!--...-->
5.点击Student组件的 ‘传递学生名给Schoo组件’ 按钮得到的效果:
消息订阅和发布
需求:还是上述的案例,将学校名称传递学生组件,使用消息订阅和发布来完成数据的传递,消息的订阅和发布需要借助第三方工具,本案例用的是 pubsub-js
插件:
- 打开命令行,进入到项目代码中,下载
pubsub-js
插件: - 订阅:谁需要数据,谁就订阅消息,在订阅消息之前需要引入
pubsub-js
插件:
Student.vue(student组件接受学校名字,所以student组件订阅消息)<template> <div class="student"> <p>学生姓名:{{name}}</p> <p>学生年龄:{{age}}</p> <p>学生性别:{{sex}}</p> <button @click = "sendStudentName">传递学生名给School组件</button> </div> </template> <script> import pubsub from 'pubsub-js';// 引入插件 export default { name:'Student', data() { return { name:"张三", age:18, sex:"男" } }, methods: { sendStudentName(){ this.$bus.$emit("StudentName",this.name); } }, mounted() { // 消息订阅,谁需要数据,谁订阅消息, //只有订阅了,发布的时候才能收到想要的数据。 // 订阅消息最好在页面挂载完毕之后立即订阅。 // msgName:消息的名字 // data:传入的参数 // this.pubId: // pubsub订阅消息返回的时消息的ID(取消订阅也需要用到此ID) // 之所以用this.pubId,因为订阅和销毁在不同作用域中,无法使用变量。 this.pubId = pubsub.subscribe("SchoolName",(msgName,data)=>{ console.log("消息名字:",msgName); console.log("Student组件收到学校名字:",data); }) }, beforeDestory(){ //订阅的消息最好在组件销毁前取消订阅。 pubsub.unsubScribe(this.pubId); } } </script>
- 订阅完消息,就等待消息的发布了,谁提供数据,谁就发布消息,并传入所需的数据。本案例中是School组件提供数据,所以在School组件内内部发布消息。触发机制是按钮点击时发布消息, 在发布消息之前也需要引入
pubsub-js
插件:
School<template> <div class="school"> <p>学校名字:{{name}}</p> <p>学校地址:{{add}}</p> <p>办校年限:{{age}}</p> <button @click = "sendSchoolName">传递学校名给Student组件</button> </div> </template> <script> import pubsub from 'pubsub-js'; export default { name:'School', props:['getSchoolName'], data() { return { name:"五道口技术学院", add:"北京", age:100 } }, methods: { sendSchoolName(){ // 发布消息:谁提供数据谁发布消息,同时传入需要的数据给消息订阅者 pubsub.publish("SchoolName",this.name); } }, mounted() { this.$bus.$on("StudentName",(name)=>{ console.log("School接收到的数据",name); }) }, } </script> <!--...-->
- 效果:(点击了‘传递学校名给Student组件’ 按钮)
- 解析:
- 引入插件后得到一个
subpub
对象 。 - 订阅消息:
pubsub.subscribe("SchoolName",(msgName,data)=>{})
- 最好在页面挂在完毕的钩子函数内部订阅:
mounted(){}
- 订阅的回调函数最好写成箭头函数,保证this指向vm。
- 回调函数接受两个参数:
- msgName 消息的名字(SchoolName)
- data:需要的数据,本案例是学校的名字
- 订阅的消息最好在组件销毁前取消订阅:
beforsDestory(){}
- 最好在页面挂在完毕的钩子函数内部订阅:
- 发布消息:
pubsub.publish("SchoolName",this.name)
。- 消息的发布一般实在一个合适的时机,比如点击时触发,同时传入需要的数据。
- 引入插件后得到一个