• Vue组件通信(组件的自定义事件、全局事件总线、消息订阅与发布、插槽、props)(八)


    vue系列文章目录

    第一章:Vue基础知识笔记(模板语法、数据绑定、事件处理、计算属性)(一)
    第二章:Vue基础知识(计算属性、监视属性、computed和watch之间的区别、绑定样式)(二)
    第三章:Vue基础知识(条件渲染、列表渲染、收集表单数据、过滤器)(三)
    第四章:Vue基础知识(内置指令、自定义指令、Vue生命周期)(四)
    第五章:Vue基础知识之组件机制(非单文件组件、单文件组件)(五)
    第六章:Vue创建脚手架(六)
    第七章:Vue使用脚手架(ref、props、mixin、插件、scoped)(七)
    第九章:Vue使用脚手架(nextTick、Vue封装的过度与动画、vue脚手架配置代理)(九)
    第十章:Vuex(十)
    第十一章:vue-router(基本使用、路由重定向、多级路由、路由命名、路由的query和params参数、路由的props配置)(十一)
    第十二章:vue-router(路由的两种工作模式、router-link的replace属性、编程式路由导航、缓存路由组件keep-alive、路由守卫【全局路由守卫、独享路由守卫、组件内路由守卫】)(十二)
    第十三章:Vue组件通信二&数据同步(v-model、属性修饰符.sync、 a t t r s 与 attrs与 attrslisteners、refs. c h i l d r e n 与 r e f s . children与refs. childrenrefs.parent、provide与inject )(十三)



    为了节省空间,样式的代码都不截取,此篇文章第二篇拓展可前往十三章

    一、组件的自定义事件

    1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

    2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

    3. 绑定自定义事件:

      • 第一种方式,在父组件中:

      • 第二种方式,在父组件中:

        <template>
        	<Demo ref="demo"/>
        </template>
        
        <script>
        	...
        	methods:{
        		test(){...}
        	}mounted(){
        	   this.$refs.demo.$on('custom',this.test)
        	}
        </script>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
      • 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

    4. 触发自定义事件: 在子组件中调用this.$emit('custom',数据)

    5. 解绑自定义事件: this.$off('custom')

    6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

    注意: 通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

    案例(代码片段):
    App.vue

    <template>
      <div class="app">
        <h1>{{ msg }},学生姓名是:{{ studentName }}</h1>
    
        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
        <!-- <Student @custom="getStudentName" @demo="m1" /> -->
    
        <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
        <Student ref="student" @click.native="show" />
      </div>
    </template>
    
    <script>
    import School from "./components/School";
    
    export default {
      name: "App",
      components: { School },
      data() {
        return {
          msg: "你好啊!",
          studentName: "",
        };
      },
      methods: {
        getStudentName(name, ...params) {
          console.log("App收到了学生名:", name, params);
          this.studentName = name;
        },
        m1() {
          console.log("demo事件被触发了!");
        },
        show() {
          alert(123);
        },
      },
      mounted() {
        this.$refs.student.$on("custom", this.getStudentName); //绑定自定义事件
        // this.$refs.student.$once('custom',this.getStudentName) //绑定自定义事件(一次性)
      },
    };
    </script>
    
    • 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
    • 41
    • 42

    Student.vue

    <template>
      <div class="student">
        <h2>学生姓名:{{ name }}</h2>
        <h2>学生性别:{{ sex }}</h2>
        <h2>当前求和为:{{ number }}</h2>
        <button @click="add">点我number++</button>
        <button @click="sendStudentlName">把学生名给App</button>
        <button @click="unbind">解绑atguigu事件</button>
        <button @click="death">销毁当前Student组件的实例(vc)</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student",
      data() {
        return {
          name: "张三",
          sex: "男",
          number: 0,
        };
      },
      methods: {
        add() {
          this.number++;
        },
        sendStudentlName() {
          //触发Student组件实例身上的atguigu事件
          this.$emit("custom", this.name, 666, 888, 900);
          // this.$emit('demo')
          // this.$emit('click')
        },
        unbind() {
          this.$off("custom"); //解绑一个自定义事件
          // this.$off(['custom','demo']) //解绑多个自定义事件
          // this.$off() //解绑所有的自定义事件
        },
        death() {
          this.$destroy(); //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
        },
      },
    };
    </script>
    
    • 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
    • 41
    • 42
    • 43

    运行结果:
    在这里插入图片描述
    在这里插入图片描述


    二、全局事件总线(GlobalEventBus)

    在这里插入图片描述

    1. 一种组件间通信的方式,适用于任意组件间通信
    2. 安装全局事件总线(指定事件总线对象):
       new Vue({
          ......
          beforeCreate() {
             Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
          },
           ......
       }) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 使用事件总线:

      • 接收数据(绑定事件): A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 提供数据(分发事件): 传递数据的组件B调用this.$bus.$emit('xxxx',数据)将数据传递给A
    1. 解绑事件: 最好在beforeDestroy钩子中,用this.$bus.$off('xxxx')去解绑当前组件所用到的事件

    案例(代码片段):
    main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线
    	},
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    App.vue

    <template>
      <div class="app">
        <h1>{{ msg }}</h1>
        <School />
        <Student />
      </div>
    </template>
    
    <script>
    import Student from "./components/Student";
    import School from "./components/School";
    
    export default {
      name: "App",
      components: { School, Student },
      data() {
        return {
          msg: "你好啊!",
        };
      },
      mounted() {
        this.$bus.$on("school", (data) => {
          console.log("我是App组件,收到了School组件的数据", data);
        });
      },
    };
    </script>
    
    • 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

    School.vue

    <template>
      <div class="school">
        <h2>学校名称:{{ name }}</h2>
        <h2>学校地址:{{ address }}</h2>
        <button @click="sendSchoolAddress">把学校地址给App组件</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "School",
      data() {
        return {
          name: "你猜",
          address: "天堂",
        };
      },
      mounted() {
        this.$bus.$on("hello", (data) => {
          console.log("我是School组件,收到了Student的数据", data);
        });
      },
      beforeDestroy() {
        this.$bus.$off("hello");
      },
      methods: {
        sendSchoolAddress() {
          this.$bus.$emit("school", this.address);
        },
      },
    };
    </script>
    
    • 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

    Student.vue

    <template>
      <div class="student">
        <h2>学生姓名:{{ name }}</h2>
        <h2>学生年龄:{{ age }}</h2>
        <button @click="sendStudentName">把学生名给School组件</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student",
      data() {
        return {
          name: "张三",
          age: 12,
        };
      },
      mounted() {
        // console.log('Student',this.x)
      },
      methods: {
        sendStudentName() {
          this.$bus.$emit("hello", this.name);
        },
      },
    };
    </script>
    
    
    • 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

    在这里插入图片描述


    三、消息订阅与发布

    • 一种组件间通信的方式,适用于任意组件间通信。

    • 使用步骤:

      • 安装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
      • 提供数据: pubsub.publish('xxx',数据)
      • 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)取消订阅
        案例(代码片段):

    School.vue

    <template>
      <div class="school">
        <h2>学校名称:{{ name }}</h2>
        <h2>学校地址:{{ address }}</h2>
      </div>
    </template>
    
    <script>
    import pubsub from "pubsub-js";
    export default {
      name: "School",
      data() {
        return {
          name: "你猜",
          address: "你猜",
        };
      },
      mounted() {
        this.pubId = pubsub.subscribe("hello", (msgName, data) => {
          console.log(this);
          console.log("有人发布了hello消息,hello消息的回调执行了", msgName, data);
        });
      },
      beforeDestroy() {
        // this.$bus.$off('hello')
        pubsub.unsubscribe(this.pubId);
      },
    };
    </script>
    
    • 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

    Student.vue

    <template>
      <div class="student">
        <h2>学生姓名:{{ name }}</h2>
        <h2>学生性别:{{ sex }}</h2>
        <button @click="sendStudentName">把学生名给School组件</button>
      </div>
    </template>
    
    <script>
    import pubsub from "pubsub-js";
    export default {
      name: "Student",
      data() {
        return {
          name: "张三",
          sex: "男",
        };
      },
      mounted() {
        // console.log('Student',this.x)
      },
      methods: {
        sendStudentName() {
          pubsub.publish("hello", 666);
        },
      },
    };
    </script>
    
    • 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

    运行结果:
    在这里插入图片描述


    四、插槽

    1. 作用:父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件
    2. 分类: 默认插槽、具名插槽、作用域插槽
    3. 理解: 父组件向子组件传递带数据的标签,当一个组件有不确定的结构时, 就需要使用 slot 技术,注意:插槽内容是在父组件中编译后, 再传递给子组件的。

    (1)默认插槽

    	父组件中:
    	        <Category>
    	           <div>html结构1</div>
    	        </Category>
    	子组件中:
    	        <template>
    	            <div>
    	               <!-- 定义插槽 -->
    	               <slot>插槽默认内容...</slot>
    	            </div>
    	        </template>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    案例(代码片段):
    App.vue

    <template>
    	<div class="container">
    		<Category title="美食" >
    			<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
    		</Category>
    
    		<Category title="游戏" >
    			<ul>
    				<li v-for="(g,index) in games" :key="index">{{g}}</li>
    			</ul>
    		</Category>
    
    		<Category title="电影">
    			<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    		</Category>
    	</div>
    </template>
    
    <script>
    	import Category from './components/Category'
    	export default {
    		name:'App',
    		components:{Category},
    		data() {
    			return {
    				foods:['火锅','烧烤','小龙虾','牛排'],
    				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
    				films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
    			}
    		},
    	}
    </script>
    
    • 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

    Category.vue

    <template>
    	<div class="category">
    		<h3>{{title}}分类</h3>
    		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    		<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
    	</div>
    </template>
    
    <script>
    	export default {
    		name:'Category',
    		props:['title']
    	}
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行结果:
    在这里插入图片描述

    (2)具名插槽

    当子组件的功能复杂时,子组件的插槽可能并非是一个。就可以用到具名插槽。
    使用方法:

    	父组件中:
    	        <Category>
    	            <template slot="center">
    	              <div>html结构1</div>
    	            </template>
    	
    				<template v-slot:footer>
    	               <div>html结构2</div>
    	            </template>
    	        </Category>
    	子组件中:
    	        <template>
    	            <div>
    	               <!-- 定义插槽 -->
    	               <slot name="center">插槽默认内容...</slot>
    	               <slot name="footer">插槽默认内容...</slot>
    	            </div>
    	        </template>
    			
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    案例(代码片段):
    App.vue

    <template>
      <div class="container">
        <Category title="美食">
          <img
            slot="center"
            src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"
            alt=""
          />
          <a slot="footer" href="http://www.atguigu.com">更多美食</a>
        </Category>
    
        <Category title="游戏">
          <ul slot="center">
            <li v-for="(g, index) in games" :key="index">{{ g }}</li>
          </ul>
          <div class="foot" slot="footer">
            <a href="http://www.atguigu.com">单机游戏</a>
            <a href="http://www.atguigu.com">网络游戏</a>
          </div>
        </Category>
    
        <Category title="电影">
          <video
            slot="center"
            controls
            src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
          ></video>
          <template v-slot:footer>
            <div class="foot">
              <a href="http://www.atguigu.com">经典</a>
              <a href="http://www.atguigu.com">热门</a>
              <a href="http://www.atguigu.com">推荐</a>
            </div>
            <h4>欢迎前来观影</h4>
          </template>
        </Category>
      </div>
    </template>
    
    <script>
    import Category from "./components/Category";
    export default {
      name: "App",
      components: { Category },
      data() {
        return {
          foods: ["火锅", "烧烤", "小龙虾", "牛排"],
          games: ["红色警戒", "穿越火线", "劲舞团", "超级玛丽"],
          films: ["《教父》", "《拆弹专家》", "《你好,李焕英》", "《尚硅谷》"],
        };
      },
    };
    </script>
    
    • 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
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    Category.vue

    
    <template>
      <div class="category">
        <h3>{{ title }}分类</h3>
        <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
        <slot name="center"
          >我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot
        >
        <slot name="footer"
          >我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot
        >
      </div>
    </template>
    
    <script>
    export default {
      name: "Category",
      props: ["title"],
    };
    </script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行结果:
    在这里插入图片描述

    (3)作用域插槽

    1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
    2. 具体编码:
    	父组件中:
    			<Category>
    				<template scope="scopeData">
    					<!-- 生成的是ul列表 -->
    					<ul>
    						<li v-for="g in scopeData.games" :key="g">{{g}}</li>
    					</ul>
    				</template>
    			</Category>
    	
    	                 <Category>
    				<template slot-scope="scopeData">
    					<!-- 生成的是h4标题 -->
    					<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
    				</template>
    			</Category>
    	子组件中:
    	        <template>
    	            <div>
    	                <slot :games="games"></slot>
    	            </div>
    	        </template>
    			
    	        <script>
    	            export default {
    	                name:'Category',
    	                props:['title'],
    	                //数据在子组件自身
    	                data() {
    	                    return {
    	                        games:['红色警戒','穿越火线','劲舞团','超级玛丽']
    	                    }
    	                },
    	            }
    
    • 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

    案例(代码片段):
    App.vue

    <template>
      <div class="container">
        <Category title="游戏">
          <template scope="atguigu">
            <ul>
              <li v-for="(g, index) in atguigu.games" :key="index">{{ g }}</li>
            </ul>
          </template>
        </Category>
    
        <Category title="游戏">
          <template scope="{games}">
            <ol>
              <li style="color: red" v-for="(g, index) in games" :key="index">
                {{ g }}
              </li>
            </ol>
          </template>
        </Category>
    
        <Category title="游戏">
          <template slot-scope="{ games }">
            <h4 v-for="(g, index) in games" :key="index">{{ g }}</h4>
          </template>
        </Category>
      </div>
    </template>
    
    <script>
    import Category from "./components/Category";
    export default {
      name: "App",
      components: { Category },
    };
    </script>
    
    • 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

    Category.vue

    
    <template>
      <div class="category">
        <h3>{{ title }}分类</h3>
        <slot :games="games" msg="hello">我是默认的一些内容</slot>
      </div>
    </template>
    
    <script>
    export default {
      name: "Category",
      props: ["title"],
      data() {
        return {
          games: ["红色警戒", "穿越火线", "劲舞团", "超级玛丽"],
        };
      },
    };
    </script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    运行结果:
    在这里插入图片描述

    五、props

    查看第七章中的内容

    六、Vuex

    查看第十章中的内容

  • 相关阅读:
    keepalived+nginx高可用 脑裂监控
    idea2023.2.3版本出现reading maven projects的进度条一直卡住的问题
    Spring Boot项目学习之通用权限管理项目03
    微信个人号如何实现自动回复呢?
    运行 Triton 示例
    基于JAVA上虞烟草物流配送系统计算机毕业设计源码+数据库+lw文档+系统+部署
    功能上线回滚考虑
    JavaSE之注解
    多国语言客服系统,打破语言障碍
    对中台的探索与思考
  • 原文地址:https://blog.csdn.net/qq_48617322/article/details/126563494