【Vue.js】Vue组件间数据通信方式

前言

vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢?首先我们需要知道在vue中组件之间存在什么样的关系, 一般来说,组件可以有以下几种关系:
在这里插入图片描述
如上图所示, A与B、A与C、B与D、C与E组件之间是父子关系;B与C之间是兄弟关系;A与D、A与E之间是隔代关系;D与E是堂兄关系(非直系亲属) 针对以上关系我们归类为:

  • 父子组件之间通信
  • 非父子组件之间通信(兄弟组件、隔代关系组件等)

组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。针对不同的使用场景,如何选择行之有效的通信方式?这是我们所要探讨的主题。本文总结了vue组件间通信的几种方式,希望对小伙伴有些许帮助。

一、 props/ $emit

父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

1、父组件向子组件传值

下面通过一个例子说明父组件如何向子组件传递数据:在子组件中如何获取父组件中的字符类型数据title:“父组件向子组件传值”,数组对象类型数据list[:{ city:‘武汉’, postil :“英雄” },{ city: ‘北京’, postil:“首都” }],对象类型数据:catalogue:{keyWord:“中国四大名著”},数组型数据:articleList: [‘红楼梦’, ‘西游记’, ‘三国演义’,“水浒传”];

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue-2.6.11</title>
  <script src="../../vue.js"></script>
</head>
<body>
  <div id="app">
    <!-- 需要用“v-bind”来告诉 Vue传给prop一个什么类型的值;
      左边":"后为父组件传入的变量名;右边为子组件props接受的变量名-->
    <root-temp  :mytitle="title" :userlist="list" :catalogue.keyWord="catalogue" :articles="articleList"></root-temp>
  </div>
  <script type="text/template" id="childTemp">
    <div>
	      <!-- 获取一个变量 -->
	    <h2>{{mytitle}}</h2>
	    <ul>
	      <!-- 获取一个数组下的对象 -->
	      <li v-for="user in userlist">
	        {{user.city}}-{{user.postil }}
	      </li>
	    </ul>
	     <!-- 获取一个对象 -->
	    <h2>{{catalogue.keyWord}}</h2>
	    <ol>
	     <!-- 获取一个数组 -->
	   <li v-for="(item, index) in articles" :key="index">{{item}}</li>
	    </ol>
  </div>
  </script>

  <script>
      //全局注册的父组件 
    Vue.component('root-temp', {
      props: ['mytitle', "userlist","catalogue","articles"],
      //子组件
      template: "#childTemp",
    })
    var vm = new Vue({
      el: "#app",
      data: {
        title: "父组件向子组件传值",
        list: [
          { city: '武汉', postil : "英雄" },
          { city: '北京', postil : "首都" }
        ],
        catalogue:{
          keyWord:"中国四大名著"
        },
        articleList: ['红楼梦', '西游记', '三国演义',"水浒传"]
      },
    })
  </script>
</body>
</html>

在这里插入图片描述
“v-bind”来告诉 Vue传给prop一个什么类型的值; 子组件用props获取数据; prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。
缺点:如果组件嵌套层次多的话,数据传递比较繁琐。

2、子组件向父组件传值(通过事件形式)

对于$emit 我觉得理解是这样的: $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数。通过一个例子,说明子组件如何向父组件传递数据。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../vue.js"></script>
</head>
<body>
  <div id="app">
    {{count}}
    <gp-counter @counterchange="handleChange"></gp-counter>
  </div>
  <script type="text/template" id="counterTemp">
    <div>
    <h1>gp-counter</h1>
    <div>
      <button @click="decrement(1)">-</button>
      <button @click="increment(1)">+</button>
    </div>
  </div>
  </script>

  <script>
    Vue.component('gp-counter', {
      template: "#counterTemp",
      //组件的data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝,不然一个实例的data改变所有实例的date的引用都会改变,:
      data() {
        return {
          //子组件将要向外发送的数据:给个初始值为0
          count: 0
        }
      },
      methods: {
      //子组件中对加减事件进行侦听,操作数据驱动显示
        increment(num) {
          this.count++;
          //数据改变时,使用$emit向外发布“counterchange”事件,并将数据放在第二个参数传递;
          this.$emit('counterchange', this.count)
        },
        decrement(num) {
          this.count--;
          this.$emit('counterchange', this.count)
        }
      }
    })

    var vm = new Vue({
      el: "#app",
      data: {
        count: 0
      },
      methods: {
        handleChange(num) {
          this.count = num;
        }
      }
    })
  </script>
</body>
</html>

在这里插入图片描述
总结:在子组件中对加减事件进行侦听,操作数据驱动显示;数据改变时,使用$emit向外发布“counterchange”事件,并将数据放在第二个参数传递;在父组件里通过@counterchange="handleChange"就可以订阅handleChange事件了;handleChange函数拿到的参数赋给实例data选项中的一个属性;就可以在父级组件之间使用。
缺点:如果组件嵌套层次多的话,数据传递比较繁琐。

二、provide/ inject

概念:provide/ inject 是vue2.0新增的api,简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量;主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。接下来就用一个例子来验证上面的描述: 假设有三个组件: A、B、C 其中 C是B的子组件,B是A的子组件

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../vue.js"></script>
</head>
<body>
  <div id="app">
    <compa></compa>
  </div>
  <script>
    //A组件
    const compa = {
      template: `
      <div><compb></compb></div>
      `,
      //B组件
      components: {
      	inject: ['user'],
        compb:{
          template: `
          <div><h2>“儿子”组件------{{user}}</h2><compc></compc></div>
      `,
      components: {
        compc
      }
        }
      }
    }
    //C组件
    const compc = {
      //利用inject实现注入变量username
      inject: ['username'],
      template: '<h2>“孙子”组件------{{username}}</h2>'
    }
    var vm = new Vue({
      el: "#app",
      data: {
        username: 'apple'
      },
      //provide函数结合data选项配置数据
      provide: function () {
        return {
          username: this.username
        }
      },
      components: {
        compa
      }
    })
  </script>
</body>

</html>

在这里插入图片描述
在这里插入图片描述
通过对比来看这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据;注意配置和注入的变量名要保持一致。
缺点:provide inject (依赖注入)不支持响应式

三、$root$parent$refs

1、$root Vue 子组件可以通过$root 属性获取vue的根实例,比如在简单的项目中将公共数据放再vue根实例上(可以理解为一个全局 store ),因此可以代替vuex实现状态管理;

2、$parent 属性可以用来从一个子组件访问父组件的实例,可以替代将数据以 prop 的方式传入子组件的方式;当变更父级组件的数据的时候,容易造成调试和理解难度增加;

3、在子组件上使用ref特性后,this.$refs 属性可以直接访问该子组件。可以代替事件$emit$on 的作用。使用方式是通过 ref 特性为这个子组件赋予一个 ID 引用,再通过this.$refs.testId获取指定元素。注意:$refs 只会在组件渲染完成之后生效,并且它们不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问 $refs
通过一个Demo具体看一下

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../vue.js"></script>
</head>

<body>
    <div id="app">
<!--父组件 -->
        <root-component></root-component>
    </div>
    <script>
//子组件
   Vue.component('root-component', {
    template: `<div>
                    <button @click='getHandler'>子组件</button>
                    <child-component></child-component>
                </div>`,
    methods: {
        getHandler() {
            console.log("root-component-->this:",this)
            console.log("root-component-->this.$root:",this.$root)
            console.log("root-component-->this.$parent:",this.$parent)
        }
    }
})
//孙子组件
Vue.component('child-component', {
    template: `<div>
                <button @click='getHandler'>孙子组件</button>
                </div>`,
    methods: {
        getHandler() {
        console.log("child-component-->this:",this)
        console.log("child-component-->this.$root:",this.$root)
        console.log("child-component-->this.$parent:",this.$parent)
    }
    }
})
var app = new Vue({
    el: '#app',
    data: {
        message: 'Root'
    }
})
    </script>
 
</body>
</html>

在这里插入图片描述
通过观察Dom结构组件有三级,分别在子组件和孙子组件中侦听click事件;在对应getHandler事件中获取对应的thisthis.$rootthis.$parent;通过对比在两级组件中的结果可以看出:
在这里插入图片描述
root 和parent 的区别
root 和parent 都能够实现访问父组件的属性和方法,两者的区别在于,如果存在多级子组件,通过parent 访问得到的是它最近一级的父组件,通过root 访问得到的是根父组件。
所有子组件都可以将这个实例作为一个全局 store 来访问或使用

// 获取根组件的数据
this.$root.message
// 写入根组件的数据
this.$root.message= 2
// 访问根组件的计算属性
this.$root.selfdate
// 调用根组件的方法
this.$root.selfmethods()

$refs 访问子组件实例;通过在子组件标签定义 ref 属性,在父组件中可以使用$refs 访问子组件实例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../vue.js"></script>
</head>

<body>

<div id="app">
    <button @click="add">通过ref访问子组件</button>
    <input type="text" ref="inputDate"/>
</div>
 <script>
var app = new Vue({
    el: '#app',
    methods: {
        add:function(){
        console.log("获取子组件的input.value---->",this.$refs.inputDate.value)
        this.$refs.inputDate.value ="test"; //this.$refs.inputDate  减少获取dom节点的消耗
        console.log("获取更改后的子组件input.value---->",this.$refs.inputDate.value)      
        }
    }
})
 </script>
</body>
</html>

在这里插入图片描述

四、eventBus

eventBus 又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。在Vue的项目中怎么使用eventBus来实现组件之间的数据通信呢?具体通过下面几个步骤

1. 实例初始化

首先需要创建一个事件总线并将其导出, 以便其他模块可以使用或者监听它.

 var eventbus = new Vue();
2. 发送事件
  methods: {
    handleClick() {
      eventbus.$emit('message', 'hello world')
    }
  }
3. 接收事件
    const compb = {
      template: '<h3>EventBus-componentb</h3>',
      mounted() {
        eventbus.$on('message', function (msg) {
          console.log(msg)
        })
      }
    }
4. 移除事件监听

如果需要移除事件的监听:

  mounted() {
    eventbus.$off('message', function (msg) {
      console.log(msg)
    })
  }

我们通过完整的Domo看一下效果:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="../../vue.js"></script>
</head>

<body>

  <div id="app">
    <compa :user="username"></compa>
    <button @click="handleClick">send</button>
  </div>
  <script>
     var eventbus = new Vue();
    const compb = {
      template: '<h3>EventBus-componentb</h3>',
      mounted() {
        eventbus.$on('message', function (msg) {
          console.log('组件中获取的message===>',msg)
        })
        eventbus.$off('message', function (msg) {
        console.log("侦听器已经移除")
        })
      }
    }
    const compa = {
      template: `
      <div>
        <h2>
          EventBus-componenta
        </h2>
        <compb></compb>
      </div>
      `,
      components: {
        compb
      }
    }
    var vm = new Vue({
      el: "#app",
      data: {
        username: 'china'
      },
      components: {
        compa
      },
      methods: {
        handleClick() {
          eventbus.$emit('message', 'hello world')
        }
      }
    })

  </script>

</body>

</html>

在这里插入图片描述
缺点:eventbus 方式数据不支持响应式;当项目较大,维护起来也比较困难。

五、Vuex

1.简要介绍Vuex原理

Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走action,但action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。

2.简要介绍各模块在流程中的功能:

Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
dispatch:操作行为触发方法,是唯一能执行action的方法。
actions:操作行为处理模块,由组件中的 $store.dispatch(‘action 名称’,data)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。
commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
mutations:状态改变操作方法,由actions中的 commit(‘mutation 名称’)来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。
state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。
getters:Vue Components通过该方法读取全局state对象。

如何要详细了解Vuex实现组件间的数据通信欢迎访问下一篇博客

总结

我们可以按组件之间的三种关系简单归纳一下组件之间的通信方式:
父子通信:
父组件向子组件传递数据可以通过 props
子组件向父组件是通过 $emit$on事件;
provide / inject
还可以通过 $root$parent$refs属性相互访问组件实例;
兄弟通信: eventbusVuex
跨级通信: eventbusVuexprovide / inject;

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值