Vue组件开发
一、 ref属性
如果在vue里,想要获取DOM对象,并且不想使用JS的原生语法,那么就可以使用ref属性。ref属性的用法:
1)在HTML元素的开始标记中,或者在Vue子组件中的开始标记中定义,相当于id的替代者
html元素上: <h1 ref="xxx"></h1> Vue组件上: <School ref="xxx"></School>
2)获取方式: 在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)this.$refs.xxx
src/components/School.vue
<template> <div class="hello"> <h1>{{ schoolName }}</h1> </div> </template> <script> export default { name: 'School', data(){ return {schoolName: "水利电力学院"} } } </script>
App.vue
<template> <div id="app"> <h1 v-text="msg" id="title" ref="title1"></h1> <button ref="btn" @click="showInfo">点我显示信息</button> <School ref="sch" id="sch1"/> </div> </template> <script> import School from './components/School.vue' export default { name: 'App', components: { School }, data(){ return {msg:"哈哈哈"} }, methods:{ showInfo(){ //console.log(document.getElementById("title")) console.log(this.$refs.title1) //真实DOM元素 console.log(this.$refs.btn) //真实DOM元素 console.log(this.$refs.sch) //School组件的实例对象(vc) console.log(document.getElementById("sch1")) } } } </script>
二、 scoped 样式
2.1 简介
所有组件的style,都会被汇总到一起,如果style中设置的样式名有重复的,那么可能就会后引入的组件的样式会覆盖掉先引入组件的样式。
此时,我们可以在组件的style开始标记里,使用scoped属性,来设置style样式只针对当前组件生效,即局部生效。
注意:App.vue不适合使用scoped属性。
2.2 案例演示
School.vue
<template> <div class="demo"> <h1>学校信息</h1> <h3>学校名称: {{ name }}</h3> <h3>学校地址:{{ address }}</h3> </div> </template> <script> export default { name:"School", data(){ return {name:"水利电力",address:"长春净月"} } } </script> <!-- scoped 是组件中的样式属性,设置后,只针对于当前组件的样式生效 --> <style scoped> .demo{ background-color:rgb(0, 174, 255); } </style>
Student.vue
<template> <div class="demo"> <h1>学生信息</h1> <h3>学生名称: {{ name }}</h3> <h3>学生年龄:{{ age }}</h3> </div> </template> <script> export default { name:"Student", data(){ return {name:"张三",age:18} } } </script> <style scoped> .demo{ background-color: rgb(38, 136, 123); } </style>
App.vue
<template> <div class="demo"> <h1>正在学习Vue</h1> <School></School> <hr> <Student></Student> </div> </template> <script> import Student from "./components/Student.vue" import School from "./components/School.vue" export default{ name:"App", components:{School,Student}, } </script> <!-- App.vue中写样式,目的是为了其所包含的内容都是该样式,因此不适合使用scoped --> <style> h1{ color:red } </style>
三、 组件通信属性
3.1 什么是组件通信
组件的数据是独立的,无法直接访问其他组件的数据;想使用其他组件的数据,就需要组件通信。组件通信,就是指组件与组件之间的数据传递。
3.2 通信解决方案
3.3 父子通信流程
-
父组件通过 props 将数据传递给子组件
-
子组件利用 $emit 通知父组件修改更新
3.4 父>子通信代码演示
父向子传值步骤
第一步:在父组件中,编写子组件标签时,添加属性和属性值,也就是键值对的形式设置属性和具体值。(发起通信)
<Student name="李四" gender="女" :age="18"/> :是v-bind的简写,表示18是表达式
第二步:子组件内部通过props配置项,来接收。(收到通信)
三种配置方式: -1).简单声明接受信息: props: ['name','gender','age'] -2).接受的同时限定类型: props:{ name:String, gender:String, age:Number } -3). 限定类型,必要性,默认值等 props:{ name:{ type:String, //约束类型 required:true //约束必要性 }, gender:{ type:String, default:"女" //约束默认值 }, age:{ type:Number, default:100 } }
第三步:子组件模板中可以直接使用props接收的值
案例演示:
父组件App.vue
<template> <div id="app"> <Student name="李四" gender="女" :age="18" x="1"/> <hr> <Student name="王五"/> </div> </template> <script> import Student from './components/Student.vue' export default { name: 'App', components: { Student }, data(){ return {msg:"哈哈哈"} }, } </script>
子组件Student.vue
<template> <div> <h1>{{msg}}</h1> <h2>学生姓名:{{ name }}</h2> <h2>学生性别:{{ gender }}</h2> <!-- <h2>学生年龄:{{ age+1 }}</h2> --> <h2>学生年龄:{{ myAge+1 }}</h2> <button @click="add">点我年龄+1</button> </div> </template> <script> export default { name: 'Student', data(){ console.log(this) return { msg:"水利电力学院的学生", // age:18, props优先级 myAge:this.age } }, methods:{ add(){ this.myAge++ } }, // 简单声明接受 // props:["name","gender","age"] // 接收的同时对数据进行类型限制 /* props:{ name:String, gender:String, age:Number } */ props:{ name:{ type:String, //约束类型 required:true //约束必要性 }, gender:{ type:String, default:"女" //约束默认值 }, age:{ type:Number, default:100 } } } </script>
注意:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
1.default和required一般不同时写(因为当时必填项时,肯定是有值的)
2.default后面如果是简单类型的值,可以直接写默认。如果是复杂类型的值,则需要以函数的形式return一个默认值
3.5 练习:
将父组件中的各种类型数据,传递给子组件。 只需要使用指令语法书写表达式即可。这种方式就可以自动推断数据类型了。
3.6 子>父通信方式1
第一种方式: 通过父组件给子组件传递函数类型的props实现子给父传递数据。
App.vue
<template> <div id="demo"> <h1> 老师名字:{{teacherName}}</h1> <h1> 学生名字:{{studentName}}</h1> <!-- 编写子组件标签时,定义属性,传入函数名 --> <Teacher :getTeacherData="getTeacherName"></Teacher> <Student :getStudentData="getStudentName"/> </div> </template> <script> import Teacher from './components/Teacher.vue' import Student from './components/Student.vue' export default { name: 'App', components: {Teacher,Student}, data:function(){ return { teacherName:'', studentName:'' } }, methods:{ // 定义函数 getTeacherName:function(value){ console.log("收到了Teacher组件传过来的名字:"+value); this.teacherName = value; }, getStudentName(value){ console.log("收到了Student组件传过来的名字:"+value); this.studentName = value; } } } </script> <style scoped> #demo{ background-color:blueviolet; border: 1px solid blueviolet; } </style>
Teacher.vue
<template>
<div>
序号:<input type="text" :value="id"> <br>
姓名:<input type="text" v-model="name"> <br>
年龄:<input type="text" :value="age"> <br>
性别:<input type="text" :value="gender"> <br>
<!-- 绑定点击事件,设置回调函数为传过来的函数 -->
<button @click="sendTeacherName">点我给父组件传值</button>
</div>
</template><script>
export default {
name:"Teacher",
data(){
return {
id:1,
name:"王老师",
age:21,
gender:'男'
}
},
// 使用props来接受传入的属性-->函数
props:['getTeacherData'],
methods:{
sendTeacherName(){
//调用传过来的函数,将名字作为参数传入
this.getTeacherData(this.name);
}
}
}
</script><style scoped>
div{
background-color: pink;
margin: 20px;
}
</style>
Student.vue
<template> <div> 序号:<input type="text" :value="id"> <br> 姓名:<input type="text" :value="name"> <br> 年龄:<input type="text" :value="age"> <br> 性别:<input type="text" :value="gender"> <br> <button @click="sendStudentName">点我给父组件传值</button> </div> </template> <script> export default { name:"Student", data(){ return { id:1001, name:"韩梅梅", age:21, gender:'女' } }, props:['getStudentData'], methods:{ sendStudentName(){ //调用传过来的函数,将名字作为参数传入 this.getStudentData(this.name); } } } </script> <style scoped> div{ background-color: skyblue; margin:20px; } </style>
四、 自定义事件
4.1 简介
除了js中的内置事件,Vue还允许开发者自定义事件。在Vue实例上有以下四个方法,来完成自定义事件的各种操作:
-
vm.$on
: 自定义事件监听的核心方法。它允许开发者在Vue实例上注册监听器,以响应特定的事件。-1).监听器注册:通过vm.$on(eventName, callback)的形式,将callback函数注册为eventName事件的监听器。当事件被触发时,callback将被调用。 -2).执行顺序:事件触发时,所有注册的回调函数将按照它们注册的顺序依次执行。
-
vm.$emit
: 自定义事件派发的核心方法。它允许开发者触发自定义事件,从而实现组件之间的通信。-1). 事件触发:通过vm.$emit(eventName)的形式,可以触发一个名为eventName的事件。 -2). 参数传递:使用vm.$emit(eventName, arg1, arg2, ...)触发事件时,可以传递多个参数。这些参数将按顺序传递给监听器的回调函数。
==vm.$emit在Vue组件系统中扮演着至关重要的角色,尤其是在子>父组件的通信中==
-1).子组件触发:子组件通过调用vm.$emit来触发一个事件,该事件可以被父组件监听。 -2).父组件监听:父组件通过在子组件的引用上使用v-on指令来监听子组件触发的事件。
-
vm.$off
:移除自定义事件监听器的方法,它提供了一种机制来清理不再需要的事件监听,从而避免潜在的内存泄漏问题。-1) 移除机制:vm.$off([event, callback]),如果提供了事件名称和回调函数,则只移除特定的监听器; -2).如果没有提供参数,则移除所有事件的所有监听器。: vm.$off() -3).灵活性:vm.$off的灵活性体现在可以根据实际情况选择移除单个监听器或全部监听器,以满足不同的开发需求。
应用场景:
-1).组件销毁:在Vue组件被销毁前,使用vm.$off移除所有注册的事件监听器,确保组件不会在销毁后继续触发事件,导致不可预期的行为。 -2).条件性监听:在某些情况下,事件监听可能只在特定条件下需要,一旦条件不再满足,就应该使用vm.$off移除监听器,以避免不必要的资源占用。
-
vm.$once
:提供了一种机制,允许事件只被监听一次,在事件触发后自动移除监听器。-1).场景应用:适用于那些只需要执行一次的事件处理,例如初始化操作或一次性资源的加载。 -2).资源管理:通过确保监听器只触发一次,vm.$once有助于避免不必要的资源消耗和潜在的内存泄漏。
4.2 子>父通信方法2:v-on绑定自定义事件
绑定原理:
在编写的子组件标签上使用v-on
或者@
绑定自定义事件,实际上是将事件绑定到子组件实例对象的VueComponent上。当有人触发了子组件上的这个事件,就会回调绑定到该事件上的函数。
App.vue
<template> <div id="demo"> <h1> 老师名字:{{teacherName}}</h1> <h1> 学生名字:{{studentName}}</h1> <!-- 第二种方式:使用v-on|@方式自定义事件。 --> <Teacher @getTeacherData="getTeacherName"/> <Student @getStudentData="getStudentName"/> </div> </template> <script> import Teacher from './components/Teacher.vue' import Student from './components/Student.vue' export default { name: 'App', components: {Teacher,Student}, data:function(){ return { teacherName:'', studentName:'' } }, methods:{ // 定义函数 getTeacherName:function(value){ console.log("收到了Teacher组件传过来的名字:"+value); this.teacherName = value; console.log(this); }, getStudentName(value){ console.log("收到了Student组件传过来的名字:"+value); this.studentName = value; } } } </script> <style scoped> #demo{ background: linear-gradient(to bottom right ,#2980b9,#ffffff); border: 1px solid #cefff1; } </style>
Teacher.vue
<template> <div> 姓名:<input type="text" v-model="name"> <br> 年龄:<input type="text" :value="age"> <br> <button @click="sendTeacherName">点我给父组件传值</button> </div> </template> <script> export default { name:"Teacher", data(){ return { name:"王老师", age:21, } }, methods:{ sendTeacherName(){ //当有人点击了子组件上的按钮时,会执行sendTeacherName函数,我们在这个函数的逻辑体中, //使用$emit方法来触发该组件实例对象VueComponent上绑定的事件,并且可以传入其他参数 //将参数传入回调函数中。 this.$emit('getTeacherData',this.name) } } } </script> <style scoped> div{ background-color: #a6acec; margin: 20px; } </style>
Student.vue
<template>
<div>
姓名:<input type="text" :value="name"> <br>
年龄:<input type="text" :value="age"> <br>
<button @click="sendStudentName">点我给父组件传值</button>
</div>
</template><script>
export default {
name:"Student",
data(){
return {
name:"韩梅梅",
age:21,
}
},
methods:{
sendStudentName(){
//当有人点击了子组件上的按钮时,会执行sendTeacherName函数,我们在这个函数的逻辑体中,
//使用$emit方法来触发该组件实例对象VueComponent上绑定的事件,并且可以传入其他参数
//将参数传入回调函数中。
this.$emit('getStudentData',this.name)
}
}
}
</script>
<style scoped>
div{
background-color:#ace7ef;
margin:20px;
}
</style>
4.3 子>父通信方法3:$on绑定自定义事件
App.vue
<template> <div id="demo"> <h1> 老师名字:{{teacherName}}</h1> <h1> 学生名字:{{studentName}}</h1> <!-- 第三种方式:使用ref和$on方式来自定义事件,更加灵活。 --> <Teacher ref="teacher"/> <Student ref="student"/> </div> </template> <script> import Teacher from './components/Teacher.vue' import Student from './components/Student.vue' export default { name: 'App', components: {Teacher,Student}, data:function(){ return { teacherName:'', studentName:'' } }, methods:{ // 定义函数 getTeacherName:function(value){ console.log("收到了Teacher组件传过来的名字:"+value); this.teacherName = value; console.log(this); }, getStudentName(value){ console.log("收到了Student组件传过来的名字:"+value); this.studentName = value; } }, mounted(){ // 通过$refs属性获取组件实例对象,然后使用$on来绑定事件,这样的方式更加灵活 this.$refs.teacher.$on('getTeacherData',this.getTeacherName) this.$refs.student.$on('getStudentData',this.getStudentName) } } </script> <style scoped> #demo{ background: linear-gradient(to bottom right ,#2980b9,#ffffff); border: 1px solid #cefff1; } </style>
4.4 总结:组件的自定义事件
1)一种组件间通信的方式,适用于:子组件 ====> 父组件通信
2)使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调函数在A中)
3)绑定自定义事件:
-
第一种方式,在父组件中:
<Demo @pClick="test"/>
或<Demo v-on:pClick="test"/>
-
第二种方式,在父组件中: 使用ref和$on
<Demo ref="demo"/>
mounted(){
this.$refs.demo.$on('pClick',data)
}
4)若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
5)触发自定义事件:this.$emit('pClick',数据)
6)解绑自定义事件:this.$off('pClick')
7)组件上也可以绑定原生DOM事件,需要使用native修饰符
<Demo @click.native=“f1()”>
注意:通过this.$refs.xxx.$on('pClick',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
4.5 全局事件总线:任意组件之间通信
全局事件总线(GlobalEventBus)是一种可以在任意组件间通信的方式,本质上就是一个对象。它必须满足以下条件:
1. 所有的组件对象都必须能看见他 2. 这个对象必须能够使用$on、$emit和$off方法去绑定、触发和解绑事件
步骤1)安装全局事件总线:
new Vue({ ... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ... })
步骤2)使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
export default { methods(){ demo(data){...} } ... mounted() { this.$bus.$on('xxx',this.demo) } }
-
提供数据:B组件想要提供数据,则在B组件中触发$bus身上的自定义事件
this.$bus.$emit('xxx',data)
建议:最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = falsenew Vue({
render: h => h(App),
beforeCreate() {
//定义一个全局总线,也就是所有组件都可以访问到的对象
Vue.prototype.$bus = this
},
}).$mount('#app')
Student.vue
<template> <div> 姓名:<input type="text" v-model="name"> <br> 年龄:<input type="text" :value="age"> <br> <button @click="sendToTeacher" >点我给Teacher组件传值</button> </div> </template> <script> export default { name:"Teacher", data(){ return { name:"韩梅梅", age:21, } }, methods:{ sendToTeacher(){ console.log("给teacher组件发送信息") this.$bus.$emit('getStudentSendData',this.name); }, getTeacherSendData(value){ console.log("获取Teacher发送过来的信息"); this.name = value; } }, mounted() { this.$bus.$on('getTeacherSendData',this.getTeacherSendData) }, beforeDestroy(){ this.$bus.$off('getTeacherSendData') } } </script> <style scoped> div{ background-color: pink; margin: 20px; } </style>
Teacher.vue
<template>
<div>
姓名:<input type="text" v-model="name"> <br>
年龄:<input type="text" :value="age"> <br>
<button @click="sendToStudent" >点我给Student组件传值</button>
</div>
</template><script>
export default {
name:"Teacher",
data(){
return {
name:"王老师",
age:21
}
},
methods:{
sendToStudent(){
console.log("给Student发送信息");
this.$bus.$emit('getTeacherSendData',this.name)
},
getStudentSendData(value){
console.log("获取Student发送过来的信息");
this.name = value;
}
},
mounted() {
this.$bus.$on('getStudentSendData',this.getStudentSendData)
},
beforeDestroy(){
this.$bus.$off('getStudentSendData')
}
}
</script><style scoped>
div{
background-color: skyblue;
margin: 20px;
}
</style>