16.Vue - 组件自定义事件、子->父组件通信

目录

一、绑定自定义事件

1.1 props方式

1.1.1 App.vue

1.1.2 School.vue

1.2 自定义事件方式 v-on

1.2.1 App.vue

1.2.2 Student.vue

1.3 自定义事件方式 ref

1.3.1 App.vue

1.3.2 Student.vue

二、解绑自定义事件

三、总结

四、TODoList 案例

4.1 MyHeader 组件 与 App 组件

4.1.1 App 组件

4.1.2 MyHeader 组件

4.2 MyFooter 组件 和 App 组件

4.2.1 App 组件

4.2.2 MyFooter组件

4.3 App 组件代码


一、绑定自定义事件

组件的自定义事件,区别与JS中内置事件而存在

JS中的事件如click、keyup、keydown,我们直接使用即可,这些事件也叫内置事件,给HTML中元素用的

而在这篇文章中我们要打造一个全新的事件,并且是给组件使用的

1.1 props方式

image-20231214175450713

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

image-20231214175816187

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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱布朗熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值