0
点赞
收藏
分享

微信扫一扫

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

自由情感小屋 09-18 18:00 阅读 49

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、路由守卫【全局路由守卫、独享路由守卫、组件内路由守卫】)(十二)


文章目录


一、组件的自定义事件

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

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

  3. 绑定自定义事件:

    • 第一种方式,在父组件中:<Demo @custom="test"/> 或 <Demo v-on:custom="test"/>

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

      <template>
      	<Demo ref="demo"/>
      </template>
      
      <script>
      	...
      	methods:{
      		test(){...}
      	}mounted(){
      	   this.$refs.demo.$on('custom',this.test)
      	}
      </script>
      
    • 若想让自定义事件只能触发一次,可以使用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>

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>

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


二、全局事件总线(GlobalEventBus)

在这里插入图片描述

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

    • 接收数据(绑定事件): A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
    methods(){
      demo(data){......}
    }
    ......
    mounted() {
      this.$bus.$on('xxxx',this.demo)
    }
    
    • 提供数据(分发事件): 传递数据的组件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 //安装全局事件总线
	},
})

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>

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>

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>

在这里插入图片描述


三、消息订阅与发布

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

  • 使用步骤:

    • 安装pubsub:npm i pubsub-js
    • 引入: import pubsub from 'pubsub-js'
    • 接收数据: A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
    methods(){
      demo(data){......}
    }
    ......
    mounted(){
      this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息}
    
    • 提供数据: 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>

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

(1)默认插槽

	父组件中:
	        <Category>
	           <div>html结构1</div>
	        </Category>
	子组件中:
	        <template>
	            <div>
	               <!-- 定义插槽 -->
	               <slot>插槽默认内容...</slot>
	            </div>
	        </template>

案例(代码片段):
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>

Category.vue

<template>
	<div class="category">
		<h3>{{title}}分类</h3>
		<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
		<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title']
	}
</script>

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

(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>
			

案例(代码片段):
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>

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>

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

(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:['红色警戒','穿越火线','劲舞团','超级玛丽']
	                    }
	                },
	            }

案例(代码片段):
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>

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>

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

五、props

查看第七章中的内容

举报

相关推荐

0 条评论