【Vue】Vue全家桶(三)Vue组件通信+Vue组件插槽

1 Vue组件通信

父向子传值:父组件设置v-bind传递数据,子组件设置props 接收数据

子向父传值: 子组件设置$emit传递数据 ,父组件设置v-on事件绑定接收数据

兄弟组件任意组件通信:通过事件车的方式传递数据, 新建一个空的全局Vue对象 EventBus,利用$emit发送 , $on接收

1.1 组件间通信基本原则

不要在子组件中直接修改父组件的状态数据
数据在哪, 更新数据的行为(函数)就应该定义在哪

1.2 vue 组件间通信方式

  • props
  • vue 的自定义事件
  • 消息订阅与发布(如: pubsub 库)
  • slot
  • vuex

1.1 props

props:让组件接收外部传过来的数据,此方式用于父组件向子组件传递数据
props传递数据原则:单向数据流,只能父传子
注意:

  • 如果需要向非子后代传递数据必须多层逐层传递
  • 兄弟组件间也不能直接props 通信, 必须借助父组件才可以
  • 所有标签属性都会成为组件对象的属性, 模板页面可以直接引用

1.父组件通过传统方式或v-bind动态绑定向子组件传送数据

 <!-- App父组件 -->
<template>
  <div>
   <!-- 传送数据一定要写在父组件的子组件标签上(通过标签属性)-->
   <!-- 1.传统方式传送数据 -->
    //<Student name='张三'/>
   <!-- 2.动态绑定传送数据(不限于形式,可能是函数)  Student.name会作为表达式自动执行 -->
    <Student :name='Student.name'/>
  </div>
</template>
<script>
//引入子组件
import Student from "./components/Student.vue";
export default {
    
    
   name: "App",
   components: {
    
     Student },
   data() {
    
    
    return {
    
    
      Student:{
    
    
        name:'张三'
      }
    };
  },
};
</script>

2.子组件内部通过props接收父组件传递的数据

 <!-- Student子组件 -->
//第一种方式(只接收)最常用
props:['name']
//第二种方式(限制类型)
props:{
    
    name:String}
//第三种方式(限制类型、限制必要性、指定默认值)
props:{
    
    
	name:{
    
    
	type:String, //类型
	required:true, //必要性
	default:'张三' //默认值
	}
}

备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

App.vue 父组件

<template>
  <div>
    <!-- 传递数据 -->
    <!-- :是v-bind动态绑定 18会作为表达式自动执行 -->
    <Student name='李四' sex='女'  :age='18'></Student>
  </div>
</template>

<script>
//引入子组件

import Student from "./components/Student.vue";
export default {
    
    
   name: "App",
   components: {
    
     Student },
};
</script>

<style>
</style>

Student.vue 子组件

<template>
  <div>
    <h2>学生姓名:{
    
    {
    
     name }}</h2>
    <h2>学生性别:{
    
    {
    
     sex }}</h2>
    <h2>学生年龄:{
    
    {
    
     myAge + 1 }}</h2>
    <button @click="updateAge">尝试修改收到的年龄</button>
  </div>
</template>

<script>
export default {
    
    
  name: "Student",
  data() {
    
    
    return {
    
    
    //若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
      myAge: this.age,
    };
  },
  methods: {
    
    
    updateAge() {
    
    
      this.myAge++;
    },
  },
  
  //接收数据  简单声明接收
 props:['name','age','sex']
 

};
</script>

在这里插入图片描述

1.2 自定义事件

自定义事件用于子组件向父组件传递数据
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
隔代组件或兄弟组件间通信此种方式不合适
1.绑定自定义事件
方式一: v-on

 <!-- App.vue父组件 -->
//   xxx为自定义事件  getStudentName为回调函数(在父组件中)
<Student @xxx="getStudentName" />
...
  methods: {
    
    
  //回调函数
    getStudentName(name) {
    
    
      this.studentName = name;
    },
  },

方式二: ref

//通过ref给Student组件打标识
<Student ref="student"  />
...
  methods: {
    
    
  //回调函数
    getStudentName(name) {
    
    
      this.studentName = name;
    },
  },
 mounted() {
    
    
    //通过$refs获取Student组件
    //在获取到的Student组件上绑定自定义事件xxx getStudentName为回调函数
    this.$refs.student.$on("xxx", this.getStudentName); //$on当...时
  },

2.触发自定义事件
方法:this.$emit(eventName, data)

 <!-- Student.vue子组件 -->
<button @click="sendStudentName">把学生名给App</button>
...
  methods: {
    
    
    sendStudentName() {
    
    
      //触发Student组件实例身上的xxx自定义事事件
      this.$emit("xxx", this.name);
    }
  },

注意

  • 解绑自定义事件:this.$off(‘xxx’)
  • 自定义事件只能触发一次:可以使用once修饰符,或$once方法。
  • 组件上也可以绑定原生DOM事件,需要使用native修饰符。
    <Student @click.native=“show” />
  • 隔代组件或兄弟组件间通信不能使用自定义事件

案例

App父组件

<template>
  <div class="app">
    <h1>{
    
    {
    
     msg }},学生姓名是:{
    
    {
    
     studentName }}</h1>
    <!-- 第一种:使用@或v-on -->
    <!-- <Student @atguigu="getStudentName" /> -->

    <!-- 第二种:使用ref -->
    <Student ref="student" @click.native="show" />
  </div>
</template>

<script>
import Student from "./components/Student";
export default {
    
    
  name: "App",
  components: {
    
     Student },
  data() {
    
    
    return {
    
    
      msg: "你好啊!",
      studentName: "",
    };
  },
  methods: {
    
    
    getStudentName(name) {
    
    
      this.studentName = name;
    },
    show() {
    
    
      alert(123);
    },
  },
  mounted() {
    
    
    this.$refs.student.$on("atguigu", this.getStudentName); //绑定自定义事件
  },
};
</script>

<style scoped>
.app {
    
    
  background-color: gray;
  padding: 5px;
}
</style>

Student.vue子组件

<template>
  <div class="student">
    <h2>学生姓名:{
    
    {
    
     name }}</h2>
    <h2>当前求和为:{
    
    {
    
     number }}</h2>
    <button @click="add">点我number++</button>
    <button @click="sendStudentName">把学生名给App</button>
    <button @click="unbind">解绑atguigu事件</button>
    <button @click="death">销毁当前Student组件的实例(vc)</button>
  </div>
</template>

<script>
export default {
    
    
  name: "Student",
  data() {
    
    
    return {
    
    
      name: "张三",
      number: 0,
    };
  },
  methods: {
    
    
    add() {
    
    
      this.number++;
    },
    sendStudentName() {
    
    
      //触发Student组件实例身上的atguigu事件
      this.$emit("atguigu", this.name);
    },
    unbind() {
    
    
      this.$off("atguigu"); //解绑一个自定义事件
      // this.$off(['atguigu','demo']) //解绑多个自定义事件
      // this.$off() //解绑所有的自定义事件
    },
    death() {
    
    
      this.$destroy(); //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
    },
  },
};
</script>

<style lang="less" scoped>
.student {
    
    
  background-color: pink;
  padding: 5px;
  margin-top: 30px;
}
</style>

在这里插入图片描述

1.3 全局事件总线(GlobalEventBus)

全局事件总线:适用于任意组件间通信。

安装全局事件总线:

new Vue({
    
    
	......
	beforeCreate() {
    
    
		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
	},
    ......
}) 

使用事件总线:

(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

methods(){
demo(data){…}
}

mounted() {
this. b u s . bus. bus.on(‘xxxx’,this.demo)
}

   (2)提供数据:this.$bus.$emit('xxxx',数据)

最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
    
    
	el:'#app',
	render: h => h(App),
	beforeCreate() {
    
    
		Vue.prototype.$bus = this //安装全局事件总线
	},
})

Student.vue

<

template>
	<div class="student">
		<h2>学生姓名:{
    
    {
    
    name}}</h2>
		<h2>学生性别:{
    
    {
    
    sex}}</h2>
		<button @click="sendStudentName">把学生名给School组件</button>
	</div>
</template>

<script>
	export default {
    
    
		name:'Student',
		data() {
    
    
			return {
    
    
				name:'张三',
				sex:'男',
			}
		},
		mounted() {
    
    
			// console.log('Student',this.x)
		},
		methods: {
    
    
			sendStudentName(){
    
    
				this.$bus.$emit('hello',this.name)
			}
		},
	}
</script>

<style lang="less" scoped>
	.student{
    
    
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

School.vue

<template>
	<div class="school">
		<h2>学校名称:{
    
    {
    
    name}}</h2>
		<h2>学校地址:{
    
    {
    
    address}}</h2>
	</div>
</template>

<script>
	export default {
    
    
		name:'School',
		data() {
    
    
			return {
    
    
				name:'尚硅谷',
				address:'北京',
			}
		},
		mounted() {
    
    
			// console.log('School',this)
			this.$bus.$on('hello',(data)=>{
    
    
				console.log('我是School组件,收到了数据',data)
			})
		},
		beforeDestroy() {
    
    
			this.$bus.$off('hello')
		},
	}
</script>

<style scoped>
	.school{
    
    
		background-color: skyblue;
		padding: 5px;
	}
</style>

1.4消息订阅与发布(PubSubJS 库)

5.1 订阅消息
PubSub.subscribe(‘msg’, function(msg, data){})
1
5.2 发布消息
PubSub.publish(‘msg’, data)
1
5.3 示例
订阅消息(绑定事件监听)

import PubSub from ‘pubsub-js’

export default {
mounted () {
// 订阅消息(deleteTodo)
PubSub.subscribe(‘deleteTodo’, (msg, index) => {
this.deleteTodo(index)
})
}
}
1
2
3
4
5
6
7
8
9
10
发布消息(触发事件)

// this.deleteTodo(this.index)
// 发布消息(deleteTodo)
PubSub.publish(‘deleteTodo’, this.index)
1
2
3
5.4 注意
优点: 此方式可实现任意关系组件间通信(数据)
5.5 事件的2 个重要操作
绑定事件监听(订阅消息)
目标: 标签元素
事件名(类型): click/focus
回调函数: function(event){}
触发事件(发布消息)
DOM 事件: 用户在浏览器上对应的界面上做对应的操作
自定义: 编码手动触发
5.6 总结
一种组件间通信的方式,适用于任意组件间通信。

使用步骤:

安装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
7
提供数据:pubsub.publish(‘xxx’,数据)

最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

学校名称:{ {name}}

学校地址:{ {address}}

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


学生姓名:{ {name}}


学生性别:{ {sex}}


<button @click=“sendStudentName”>把学生名给School组件

1.3 非父子组件间传值

在这里插入图片描述
1、任意组件之间传递数据需要借助于事件车,通过事件车的方式传递数据
2、创建一个Vue的实例,让各个组件共用同一个事件机制。
3、传递数据方,通过一个事件触发eventBus. e m i t ( 方 法 名 , 传 递 的 数 据 ) 。 4 、 接 收 数 据 方 , 通 过 m o u n t e d ( ) 触 发 e v e n t B u s . emit(方法名,传递的数据)。 4、接收数据方,通过mounted(){}触发eventBus. emit)4mounted()eventBus.on (方法名,function(接收数据的参数){用该组件的数据接收传递过来的数据}),此时函数中的this已经发生了改变,可以使用箭头函数。

首先创建一个单独的js文件bus.js,内容如下

import Vue from 'vue'
export default new Vue

父组件如下:

<template>
     <components-a></components-a>
     <components-b></components-b>
</template>

子组件a如下:

<template>
      <div>
           <button @click="abtn">A按钮</button>
      </div>
</template>
<script>
import  from '../../js/bus.js'
export default {
      
      
      name: 'components-a',
      data () {
      
      
        return {
      
      
                ‘msg':"我是组件A"
        }
      },
      methods:{
      
      
           abtn:function(){
      
      
                  let  _this = this;
                   //$emit这个方法会触发一个事件
                   bus .$emit("myFun", _this.msg)  
           }
      }
}
</script>

子组件b如下:

<template>
     <div>
         <div>{
   
   {btext}}</div>
     </div>
</template>
<script>
import bus from '../../js/bus.js'
export default {
      
      
   name: 'components-b',
   data () {
      
      
        return {
      
      
           'btext':"我是B组件内容"
        }
   },
   created:function(){
      
      
       this.bbtn();
   },
   methods:{
      
      
       bbtn:function(){
      
      
            bus .$on("myFun",(message)=>{
      
         
            //这里最好用箭头函数,不然this指向有问题
                 this.btext = message      
            })
       }
    }
}
</script>

这样的话点击组件A里面的按钮,组件B即可获取A组件传来的值。

2 组件插槽

匿名插槽 唯一存在 <slot></slot>
具名插槽 可存在多个 <slot name="up"></slot>
作用域插槽:
插槽的使用:子组件定义插槽的位置,父组件定义插槽的内容, 控制内容的显示与隐藏

2.1 组件插槽的作用

编译作用域:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
插槽的目的是让我们原来的设备具备更多的扩展性。比如电脑的USB我们可以插入U盘。
组件插槽是Vue的内置组件,为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么。
组件插槽的作用:父组件向子组件传递内容
在这里插入图片描述

2.2 组件插槽基本用法

在子组件中,使用特殊的元素<slot>就可以为子组件开启一个插槽。

该插槽插入什么内容取决于父组件如何使用。

如何封装合适呢?

抽取共性,保留不同。最好的封装方式就是将共性抽取到组件中,将不同预留为插槽。

1.插槽位置

Vue.component('alert-box', {
	template: `
		<div class="demo-alert-box">
			<strong>Error!</strong>
			<slot></slot>
		</div>
	`
})

2.插槽内容

<alert-box>Something had happened.</alert-box>
//12-插槽基本用法.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">

    Vue.component('alert-box', {
    
    
      template: `
        <div>
          <strong>ERROR:</strong>
          <slot>默认内容</slot>
        </div>
      `
    });
    var vm = new Vue({
    
    
      el: '#app',
      data: {
    
    
        
      }
    });
  </script>
</body>
</html>

在这里插入图片描述

2.3 具名插槽用法

1. 插槽定义

<div class="container">
	<header>
		<slot name="header"></slot>
	</header>
	<main>
		<slot></slot>
	</main>
	<footer>
		<slot name="footer"></slot>
	</footer>
</div>

2.插槽内容

//根据名称进行匹配,没有匹配到的放在默认插槽中
<base-layout>
	<h1 slot="header">标题内容</h1>
	<p>主要内容1</p>
	<p>主要内容2</p>
	<p slot="footer">底部内容</p>
</base-layout>
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <base-layout>
      <template slot='header'>
        <p>标题信息1</p>
        <p>标题信息2</p>
      </template>
      <p>主要内容1</p>
      <p>主要内容2</p>
      <template slot='footer'>
        <p>底部信息信息1</p>
        <p>底部信息信息2</p>
      </template>
    </base-layout>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    Vue.component('base-layout', {
      
      
      template: `
        <div>
          <header>
            <slot name='header'></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name='footer'></slot>
          </footer>
        </div>
      `
    });
    var vm = new Vue({
      
      
      el: '#app',
      data: {
      
      

      }
    });
  </script>
</body>

</html>

在这里插入图片描述

2.4 作用域插槽

应用场景:父组件对子组件的内容进行加工处理

1.插槽定义

<ul>
	<li v-for= "item in list" v-bind:key= "item.id" >
		<slot v-bind:item="item">
			{
    
    {
    
    item.name}}
		</slot>
	</li>
</ul>

2.插槽内容

<fruit-list v-bind:list= "list">
	<template slot-scope="slotProps">
		<strong v-if="slotProps.item.current">
			{
    
    {
    
     slotProps.item.text }}
		</strong>
	</template>
</fruit-list>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<style type="text/css">
  .current {
      
      
    color: orange;
  }
</style>
<body>
  <div id="app">
    <fruit-list :list='list'>
      <template slot-scope='slotProps'>
        <strong v-if='slotProps.info.id==3' class="current">{
   
   {slotProps.info.name}}</strong>
        <span v-else>{
   
   {slotProps.info.name}}</span>
      </template>
    </fruit-list>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    Vue.component('fruit-list', {
      
      
      props: ['list'],
      template: `
        <div>
          <li :key='item.id' v-for='item in list'>
            <slot :info='item'>{
       
       {item.name}}</slot>
          </li>
        </div>
      `
    });
    var vm = new Vue({
      
      
      el: '#app',
      data: {
      
      
        list: [{
      
      
          id: 1,
          name: 'apple'
        },{
      
      
          id: 2,
          name: 'orange'
        },{
      
      
          id: 3,
          name: 'banana'
        }]
      }
    });
  </script>
</body>
</html>

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Better_Xing/article/details/124302071