目录
一、绑定自定义事件
组件的自定义事件,区别与JS中内置事件而存在
JS中的事件如click、keyup、keydown,我们直接使用即可,这些事件也叫内置事件,给HTML中元素用的
而在这篇文章中我们要打造一个全新的事件,并且是给组件使用的
1.1 props方式
1.1.1 App.vue
<template>
<div class="app">
<h1>{{ msg }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"></School>
</div>
</template>
<script>
//引入组件
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg: "你好啊!",
};
},
methods:{
getSchoolName(name){
console.log('App收到了学校名:',name)
}
},
components: {
Student,
School,
},
};
</script>
<style scoped>
.app {
background-color: gray;
padding: 5px;
}
</style>
1.1.2 School.vue
<template>
<div class="school">
<h2 >学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
//组件名称
name: "School",
props:['getSchoolName'],
data() {
return {
name: "齐工大",
address: "济南",
};
},
methods:{
sendSchoolName(){
this.getSchoolName(this.name)
}
}
};
</script>
<style scoped>
.school {
background: skyblue;
padding: 5px;
margin-top: 30px;
}
</style>
1.2 自定义事件方式 v-on
1.2.1 App.vue
<template>
<div class="app">
<h1>{{ msg }}</h1>
<!-- v-on 绑定事件,且v-on给testDemo身上绑定事件 -->
<!-- 准确的来说是给组件实例对象vc身上绑定一个testDemo事件 -->
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据 -->
<Student v-on:testDemo="getStudentName"></Student>
</div>
</template>
<script>
//引入组件
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg: "你好啊!",
};
},
methods:{
getStudentName(name){
console.log('demo被调用了',name)
}
},
components: {
Student,
School,
},
};
</script>
<style scoped>
.app {
background-color: gray;
padding: 5px;
}
</style>
假如我们只能执行第一次,之后不能执行
<Student v-on:testDemo.once="getStudentName"></Student>
1.2.2 Student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给App</button>
</div>
</template>
<script>
export default {
//组件名称
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods:{
sendStudentName(){
//this可以拿到组件实例对象
//$emit函数的含义是想触发哪个事件,我们指定的是testDemo事件
//触发Student组件实例身上的testDemo事件
//从第二个参数开始就可以传参
this.$emit('testDemo',this.name)
}
}
};
</script>
<style scoped>
.student {
background: orange;
padding: 5px;
margin-top: 30px;
}
</style>
1.3 自定义事件方式 ref
这种的使用灵活性更强
1.3.1 App.vue
<Student ref="student"></Student>
methods:{
getStudentName(name){
console.log('demo被调用了',name)
}
},
mounted(){
// this.$refs.student 这一部分相当于获取到了组件实例对象
// $on 表示当...时候 ,$on('testDemo')表示当testDemo事件触发的时候执行getStudentName回调函数
//this.getStudentName,如果有括号就报错
this.$refs.student.$on('testDemo',this.getStudentName)
}
假如我们只能执行第一次,之后不能执行
this.$refs.student.$once('testDemo',this.getStudentName)
1.3.2 Student.vue
<button @click="sendStudentName">把学生名给App</button>
methods:{
sendStudentName(){
//this可以拿到组件实例对象
//$emit函数的含义是想触发哪个事件,我们指定的是testDemo事件
//触发Student组件实例身上的testDemo事件
//从第二个参数开始就可以传参
this.$emit('testDemo',this.name)
}
}
二、解绑自定义事件
在Student.vue组件中进行解绑
<button @click="unbind">解绑testDemo事件</button>
unbind() {
// 这个语句只能解绑一个自定义事件
this.$off("testDemo");
// this.$off(['testDemo','demo']) 解绑多个事件,有几个事件,就往里面写几个事件
// this.$off() 解绑所有的自定义事件
},
三、总结
子组件找一个合适的机会触发一下就可以向父组件传递数据
-
组件自定义事件适用于 子组件向父组件通信
-
使用场景
A是父组件,B是子组件,B想给A传数据,那么就要在A中给B组件绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件
-
第一种方式
在父组件中:直接定义标签
<Demo @ateuigu="test"/> 或<Demo y-on:atouigu="test"/>
子组件中进行调用
this.$emit('test',this.name)
-
第二种方式
在父组件中
<Student ref="student"></Student>
methods:{ getStudentName(name){ console.log('demo被调用了',name) } }, mounted(){ // this.$refs.student 这一部分相当于获取到了组件实例对象 // $on 表示当...时候 ,$on('testDemo')表示当testDemo事件触发的时候执行getStudentName回调函数 //this.getStudentName,如果有括号就报错 this.$refs.student.$on('testDemo',this.getStudentName) }
子组件中进行调用
this.$emit('testDemo',this.name)
-
第三种方式
若想让自定义事件只能触发一次, 可以使用once修饰符,或$once方法
父组件中
this.$refs.student.$once('testDemo',this.getStudentName)
-
-
触发自定义事件
this.$emit('要触发的自定义事件',数据),比如Student中组件
sendStudentName() { //this可以拿到组件实例对象 //$emit函数的含义是想触发哪个事件,我们指定的是testDemo事件 //触发Student组件实例身上的testDemo事件 //从第二个参数开始就可以传参 this.$emit("testDemo", this.name); }
-
解绑自定义事件
this.$off
unbind() { // 这个语句只能解绑一个自定义事件 this.$off("testDemo"); // this.$off(['testDemo','demo']) 解绑多个事件,有几个事件,就往里面写几个事件 // this.$off() 解绑所有的自定义事件 },
-
组件触发原生DOM事件
App.vue组件
<Student @click="show"/>
methods:{
show(){
alert("组件事件被触发")
}
}
但是仅仅有这个是完全不够的,因为Vue把 @click="show"认为是我们自定义事件
Student.vue组件
所以我们需要在Student组件中触发一下,这样就可以点击Student组件中的任意一个位置就可以触发show的回调函数了
<button @click="sendStudentName">把学生名给App</button>
sendStudentName() {
this.$emit("click", this.name);
},
但是我们不希望这么使用,而是借助native
这样以后我们点击组件中的任何一个地方都可以触发show回调函数了
<button @click.native="sendStudentName">把学生名给App</button>
说明
在App.vue组件中下面一段代码是不可以的
我们将getStudentName方法直接写在了$on的回调函数function里面
为什么不可以这么写?
因为this.studnetName = name中的this指代的是Student组件的而不是App组件里的,很神奇吧
谁触发的testDemo事件,回调函数function中的this指代的就是谁
我们之前是将getStudentName函数写在methods里面,只要不是箭头函数,this指代的都是本组件的实例对象
// methods:{
// getStudentName(name){
// console.log('demo被调用了',name)
// }
// },
data(){
return {
studnetName : ""
}
},
mounted(){
// this.$refs.student 这一部分相当于获取到了组件实例对象
// $on 表示当...时候 ,$on('testDemo')表示当testDemo事件触发的时候执行getStudentName回调函数
this.$refs.student.$on('testDemo',function(name,...params){
this.studnetName = name
})
}
但是下面这么写就是可以的
下面的回调函数写成箭头函数,就是本组件的vm实例了
因为箭头函数没有自己的this,就往外找,然后就找到本组件的实例了
mounted(){
this.$refs.student.$on('testDemo',(name,...params)=>{
this.studnetName = name
})
}
四、TODoList 案例
15.Vue - webStorage浏览器本地存储_vue的localstorage和storage语法-CSDN博客
紧接这篇文章
使用自定义事件完成子与父通信的修改
4.1 MyHeader 组件 与 App 组件
4.1.1 App 组件
<MyHeader @addTodo="addTodo"/>
自定义事件回调函数
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
4.1.2 MyHeader 组件
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
按钮点击函数中执行了自定义组件并向父组件传递了函数
methods: {
add(){
//校验数据
if(!this.title.trim()) return alert('输入不能为空')
//将用户的输入包装成一个todo对象
const todoObj = {id:nanoid(),title:this.title,done:false}
//通知App组件去添加一个todo对象
this.$emit('addTodo',todoObj,1,2,3)
//清空输入
this.title = ''
}
},
MyHeader组件所有代码
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
name:'MyHeader',
data() {
return {
//收集用户输入的title
title:''
}
},
methods: {
add(){
//校验数据
if(!this.title.trim()) return alert('输入不能为空')
//将用户的输入包装成一个todo对象
const todoObj = {id:nanoid(),title:this.title,done:false}
//通知App组件去添加一个todo对象
this.$emit('addTodo',todoObj,1,2,3)
//清空输入
this.title = ''
}
},
}
</script>
<style scoped>
......
</style>
4.2 MyFooter 组件 和 App 组件
4.2.1 App 组件
<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
自定义事件的回调函数
//全选or取消全选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
//清除所有已经完成的todo
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
4.2.2 MyFooter组件
主要是全选所有、取消所有全选、清除已完成任务
<template>
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> -->
<input type="checkbox" v-model="isAll"/>
</label>
<span>
<span>已完成{{doneTotal}}</span> / 全部{{total}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'MyFooter',
props:['todos'],
computed: {
//总数
total(){
return this.todos.length
},
//已完成数
doneTotal(){
//此处使用reduce方法做条件统计
/* const x = this.todos.reduce((pre,current)=>{
console.log('@',pre,current)
return pre + (current.done ? 1 : 0)
},0) */
//简写
return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
},
//控制全选框
isAll:{
//全选框是否勾选
get(){
return this.doneTotal === this.total && this.total > 0
},
//isAll被修改时set被调用
set(value){
// this.checkAllTodo(value)
this.$emit('checkAllTodo',value)
}
}
},
methods: {
/* checkAll(e){
this.checkAllTodo(e.target.checked)
} */
//清空所有已完成
clearAll(){
// this.clearAllTodo()
this.$emit('clearAllTodo')
}
},
}
</script>
<style scoped>
......
</style>
4.3 App 组件代码
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader @addTodo="addTodo"/>
<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
</div>
</div>
</div>
</template>
<script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter.vue'
export default {
name:'App',
components:{MyHeader,MyList,MyFooter},
data() {
return {
//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
todos:JSON.parse(localStorage.getItem('todos')) || []
}
},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选or取消勾选一个todo
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id) todo.done = !todo.done
})
},
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter( todo => todo.id !== id )
},
//全选or取消全选
checkAllTodo(done){
this.todos.forEach((todo)=>{
todo.done = done
})
},
//清除所有已经完成的todo
clearAllTodo(){
this.todos = this.todos.filter((todo)=>{
return !todo.done
})
}
},
watch: {
todos:{
deep:true,
handler(value){
localStorage.setItem('todos',JSON.stringify(value))
}
}
},
}
</script>
<style>
.......
</style>