【Vue.js】vue2组件间通信方式总结

目录

一.props传参

1.父组件 ==> 子组件

2.子组件 ==> 父组件

二.组件的自定义事件

三.全局事件总线 

四.消息订阅和发布 

五.vuex

六.插槽

1.默认插槽

2.具名插槽

3.作用域插槽

七.v-model

八.sync修饰符 

九.$attrs和$listeners属性

十.$children和$parent属性


    vue项目中大量采用组件化开发,组件之间的通信传递数据是必不可少的一个环节,并且随着需求的不同,需要进行不同关系的组件之间的值传递,从而出现各种组件通信的方式。

    组件和组件之间的关系常见有父子组件,兄弟组件,两类关系的组件通信适用的方法是不同的,方法使用的不当,极有可能事倍功半;最常见的父子组件通信方式有props传参,常用的兄弟组件通信方式有自定义事件全局事件总线;较大型的项目,一般采用的是vuex统一管理数据;此外,在一些组件库中,也经常会使用到其它几种通信方式,比如element-ui中,当你需要在一个el-table-column中插入一张图片时,就需要使用作用域插槽,将要展现的页面结构传递给子组件;再比如,在el-dialog弹出框组件中,会使用sync修饰符,获取控制组件显示或隐藏的标志符等等。以下10种组件通信方式总结如下。

一.props传参

适用于父子组件之间传递参数

​ (1).父组件 ==> 子组件 通信

​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数

1.父组件 ==> 子组件

传递数据:在子组件标签上写明传递的参数名=“参数”,若参数是js表达式,需要使用v-bind

<Demo :name="xxx"/>

接收数据:在子组件内部

1. 第一种方式(只接收)

props:['name'] 

2. 第二种方式(限制类型)

props:{name:String}

3. 第三种方式(限制类型、限制必要性、指定默认值):

props:{
    name:{
        type:String, //类型
      ​  required:true, //必要性
      ​  default:'老王' //默认值
    }
}

2.子组件 ==> 父组件

   若使用props在父组件中获取子组件的数据:这里在父组件中将addTodo()方法传递给子组件

<template>
	<div id="root">
		<MyHeader :addTodo="addTodo"/>
	</div>
</template>

<script>
	import MyHeader from './components/MyHeader'
	export default {
		name:'App',
		components:{MyHeader},
		data() {
			return {
				todos:[
					{id:'001',title:'xx',done:true},
				]
			}
		},
		methods: {
			//添加一个todo
			addTodo(todoObj){
				this.todos.unshift(todoObj)
			},
		}
	}
</script>

在子组件内部获取input输入框的输入后,将输入信息封装成一个对象todoObj,调用父组件传来的addTodo()方法 ,以实参的形式将对象todoObj传给父组件,实现子 --> 父传参

<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',
		//接收从App传递过来的addTodo
		props:['addTodo'],
		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.addTodo(todoObj)
				//清空输入
				this.title = ''
			}
		},
	}
</script>

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

二.组件的自定义事件

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

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

3. 使用 

绑定事件:在父组件中使用@自定义事件名 ,不用写形参,事件回调的形参会自动接收最组件传递的实参内容

<template>
   <Demo @xxx="test">
   或
   <Demo v-on:xxx="test">
</template>
    
<script>
   export default {
      methods:{
          test(事件参数1,...事件参数2-n){
                    
          }
      }
    	
   }        
</script>

或使用ref获取子组件实例对象,调用组件实例.$on('事件名',回调函数)用于监听子组件中定义的xxx事件;

    <template>
    	<Demo ref="demo"/>
    </template>
    
    <script>
        export defalut {
            mounted{
                this.$refs.demo.$on('xxx',this.test)
            },
            methods:{
                test(事件参数1,...事件参数2-n){
    
                }
            }
        }
    </script>

若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

触发事件:数据可以传递一个或多个,多个数据可以传递多个实参(第二个参数开始),或者写成一个对象

this.$emit('xxx',数据);

解绑事件

this.$off('xxx'); // 解绑指定事件
this.$off(['a','b']);  // 解绑多个
this.$off();  // 解绑全部

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

销毁了当前的组件实例对象时,其身上的所有自定义事件全部销毁了;销毁了vue实例时,其所有的子组件全部销毁了,子组件的自定义世事件也被销毁了,但所有的原生事件不会销毁

三.全局事件总线 

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

 安装全局事件总线:

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

由于vc.prototype.__ proto __ = vm.prototype,所以组件实例是可以访问到vue实例上的属性的,可以访问到$bus

使用事件总线 

1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身,事件的绑定一般在mounted钩子中进行,事件只需绑定一次即可,这和原生js中给DOM元素绑定事件的回调是一个道理。

methods(){
    demo(data){......}
},
......
mounted() {
    this.$bus.$on('xxxx',this.demo)
}

2. 提供数据

this.$bus.$emit('xxxx',数据)

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

四.消息订阅和发布 

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

订阅方是数据的获取方,而发布方是数据的提供方;

使用步骤:

   1. 安装:pubsub:npm i pubsub-js

   2. 引入:import pubsub from 'pubsub-js'

   3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

methods(){
  demo(data){......}
}
......
mounted() {
  this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
}

  4.提供数据:

pubsub.publish('xxx',数据)

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

五.vuex

1.概念

在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.使用场景

多个组件需要共享数据时

原理图:vuex的核心主要是由Actions,Mutations和state三部分组成,vuex做的是状态管理,实际上就是存放多个组件可以共享的数据,这些数据存放在state中,一般存放的可以是服务器返回的信息。

在下图中,Vue Components 就是vue组件,在组件中使用this.$store.dispatch('xxxAction') 调用某个action执行,一般在action中请求服务器接口(Backend API),获取请求得到的异步结果,但action无法直接操作state存放数据,所以此时它会调用指定的mutation,在mutation的内就可以操作state修改数据,最后在各个组件内使用this.$store.state.xxx 拿到state存放的状态;

当然,如果没有额外的业务逻辑需要处理,组件无需派发actions,可以在组件内直接使用this.$store.commit('xxxmutation'),通知mutations修改状态。


 

3.搭建vuex环境 

0. 安装:npm i vuex

1. 创建文件:src/store/index.js

   必须得在store实例创建前使用Vuex插件,所以Vue.use(Vuex)必须写在index.js中,而不是main.js中。

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
   
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
// 准备getters 对state中的内容做二次处理,类似于计算属性
const getters = {}
   
//创建并暴露store
export default new Vuex.Store({
 actions,
 mutations,
 state
})

2. 在main.js中创建vm时传入store配置项

//引入store
import store from './store'
......
   
//创建vm
new Vue({
 el:'#app',
 render: h => h(App),
 store
})

 这样就可以在vue实例和组件实例中看到$store属性了。

4.vuex的具体使用

1. 初始化数据、配置actions、配置mutations,操作文件store.js

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
   
const actions = {
    //响应组件中加的动作
    jia(context,value){
      // console.log('actions中的jia被调用了',miniStore,value)
   		context.commit('JIA',value)
   	},
 }
   
const mutations = {
  //执行加
  IA(state,value){
     // console.log('mutations中的JIA被调用了',state,value)
     state.sum += value
  }
}
   
//初始化数据
const state = {
   sum:0
}
   
//创建并暴露store
export default new Vuex.Store({
 actions,
 mutations,
 state,
})

2.组件中读取vuex中的数据:

$store.state.sum

3.组件中修改vuex中的数据:

$store.dispatch('action中的方法名',数据)
或
$store.commit('mutations中的方法名',数据)

备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

action中的方法第一个参数context还包括dispatch属性,即当业务逻辑较多可以链式调用多个action方法处理一个业务 

const actions = {
    //响应组件中加的动作
	jia(context,value){
		// console.log('actions中的jia被调用了',miniStore,value)
        value = value + 1;
		context.dispatch('jia1',value)
	},
    jia1(context,value) {
        value = value * 2;
        context.dispatch('jia2',value);
    },
    jia2(context,value) {
        value = value * 3;
        context.commit('JIA',value);
    },
    
}

六.插槽

作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于

            父组件 ===> 子组件

分类:默认插槽、具名插槽、作用域插槽

使用方式:

1.默认插槽

父组件中:

<Category>
    <div>html结构1</div>
</Category>

子组件中:

<template>
   <div>
      <!-- 定义插槽 -->
      <slot>插槽默认内容...</slot>
    </div>
</template>

2.具名插槽

 一个组件多个插槽,给每个插槽命名加以区分,子组件name属性对应父组件的slot属性

父组件中:

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

3.作用域插槽

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
具体编码:父组件通过scope属性值获取子组件的数据,作用域插槽和具名插槽可以结合使用。

父组件中:

<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" slot="s1">
        <!-- 生成的是h4标题 -->
        <h4 v-for="g in scopeData.games" :key="g">{
   
   {g}}</h4>
    </template>
</Category>

子组件中:

<template>
    <div>
        <slot :games="games" name="s1"></slot>
    </div>
</template>
         		
<script>
    export default {
        name:'Category',
        props:['title'],
        //数据在子组件自身
        data() {
             return {
                 games:['红色警戒','穿越火线','劲舞团','超级玛丽']
             }
        },
    }
</script>

七.v-model

 v-model实现的是双向数据绑定,常用于表单元素;但也可以用于自定义组件标签上,实现父子组件的通信,父子组件间的数据同步

首先这里用v-bind和input事件实现一个v-model

</template>
    <input type="text" :value="text" @input="text=$event.target.value">
</template>

<script>

export default {
    data(){
        return{
          text:0
        }
     },
}

其能实现v-model一样的效果,这里的value是input表单元素的属性,@input是表单的输入事件;如果一样的写法,写在组件标签上那么value就是组件的props参数,而@input则是自定义事件,若依旧i将@input作为原生的输入事件需要添加事件修饰符,即@input.native

接下去创建一个Inputs组件,给其传递一个参数value,并给其绑定一个自定义事件input

</template>
    <Inputs :value="text" @input="text=$event"/>
    <Inputs v-model="text"/>
</template>

<script>

export default {
    data(){
        return{
          text:0
        }
     },
}

在组件内部接收参数value,触发自定义时间内input,将表单的输入内容$event.target.value作为自定义事件的参数传递。

<template>
    <div>
        <span>input:</span>
        <input type="text" :value="value" @input="$emit('input',$event.target.value)">
    </div>
</template>

<script>
export default {
    props:['value']
}
</script>

其同样可以实现上面一样的双向数据绑定的效果,并且使用v-model替换组件的:value和@input也是等价的。

八.sync修饰符 

可以实现父子组件数据的同步

<template>
    <Money :money.sync="money"/>
    <!--等价于 -->
    <Money :money="money" @update:money="money=$event">
</template>

<script>
import Money from './components/Money.vue'
export default {
    data(){
        return {
            money:10000
        }
    },
    components:{
        Money
    }
}
</script>

:money.async代表父组件给子组件传递字符串props[money],给当前子组件绑定一个自定义事件(update:money),子组件如下:

<template>
  <div>
      <span>花费了100元</span>
      <button @click="$emit('update:money',money-100)">花钱</button>
      <span>还剩{
   
   {money}}元</span>
  </div>
</template>

<script>
export default {
    props:['money']
}
</script>

九.$attrs和$listeners属性

$attrs是组件实例的一个属性,可以获取父组件传递过来的props数据

$listeners是组件实例的属性,获取父组件给子组件传递的自定义事件;

如果子组件通过props接收的属性,在$attrs属性当中是获取不到的;

console.log(this.$attrs):

console.log(this.$listeners)  

使用el-button封装一个组件,使其具有提示功能

父组件:

<template>
    <ElButton type="success" icon="el-icon-delete" size="mini" title="按钮" @click="handler"/>
</template>
<script>
import ElButton from './components/elButton.vue'
export default {
    components:{
        ElButton
    },
    methods:{
        handler(){
          console.log('123')
        },
    }
}
</script>

子组件:可以采用type=”$attrs.type“ size="$attrs.size"......的写法,但像这样过于繁琐,如果传递的属性名和绑定标签的属性名是一致的,直接使用v-bind="$attrs"的形式,需要注意的是,不能写成:=''$attrs";

同理v-on不能用@进行替换

<template>
  <a :title="title">
      <el-button v-bind="$attrs" v-on="$listeners"></el-button>
  </a>
</template>

<script>
export default {
    props:['title'],
    mounted(){
        console.log(this.$attrs);
    }
}
</script>

十.$children和$parent属性

$children是组件实例的属性,可以获取到当前组件的全部子组件,返回的是一个数组

$parent属性获取到某一组件的父组件,可以操作父组件的数据和方法

现在有如下示例:

 点击父组件按钮,父组件的存款要加100,同时子组件的存款要减100,父组件通过$children获取子组件的实例,由于只存在一个子组件,所以$children的索引0就是子组件的实例,从而可以让子组件的data中的money减少100。

<template>
  <div>
    <h2>爸爸有存款:{
   
   {money1}}</h2>
    <button @click="borrowM(100)">找小明借钱100</button>
    <Son/>
  </div>
</template>

<script>
import Son from './components/Son.vue'
export default {
    data(){
        return{
          money1:1000
        }
     },
    methods:{
        borrowM(money){
          this.money1 += money;
          this.$children[0].money -= money;
        },
    },
    components:{
        Son
    }
}

如下,在子组件Son中,点击按钮,子组件中的存款减少50,同时父组件中的存款增加50,可以通过$parent获取父组件,从而操作父组件的data中的属性money1让其增加50. $parent返回的就是父组件实例对象。

<template>
    <div>
        <h3>儿子小明:现有存款:{
   
   {money}}</h3>
        <button @click="giveMoney(50)">给爸爸50元</button>
    </div>
</template>

<script>
export default {
    data(){
        return {
            money:1000
        }
    },
    methods:{
        giveMoney(money){
            this.money -= money;
            // 获取到父组件,让父组件的数据加50
            this.$parent.money1 += money;
        }
    }
}
</script>

猜你喜欢

转载自blog.csdn.net/weixin_43655896/article/details/122590164