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

前言

vue是数据驱动视图更新的框架, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢?首先我们需要知道在vue中组件之间存在什么样的关系, 一般来说,组件可以有以下几种关系:
【Vue.js】Vue组件间数据通信方式_第1张图片
如上图所示, 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>

【Vue.js】Vue组件间数据通信方式_第2张图片
“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>

【Vue.js】Vue组件间数据通信方式_第3张图片
总结:在子组件中对加减事件进行侦听,操作数据驱动显示;数据改变时,使用$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: `
      
`
, //B组件 components: { inject: ['user'], compb:{ template: `

“儿子”组件------{ {user}}

`
, components: { compc } } } } //C组件 const compc = { //利用inject实现注入变量username inject: ['username'], template: '

“孙子”组件------{ {username}}

'
} var vm = new Vue({ el: "#app", data: { username: 'apple' }, //provide函数结合data选项配置数据 provide: function () { return { username: this.username } }, components: { compa } }) </script> </body> </html>

【Vue.js】Vue组件间数据通信方式_第4张图片
【Vue.js】Vue组件间数据通信方式_第5张图片
通过对比来看这里不论子组件嵌套有多深, 只要调用了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: `
`
, 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: `
`
, 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>

【Vue.js】Vue组件间数据通信方式_第6张图片
通过观察Dom结构组件有三级,分别在子组件和孙子组件中侦听click事件;在对应getHandler事件中获取对应的thisthis.$rootthis.$parent;通过对比在两级组件中的结果可以看出:
【Vue.js】Vue组件间数据通信方式_第7张图片
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>

【Vue.js】Vue组件间数据通信方式_第8张图片

四、eventBus

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

1. 实例初始化

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

 var eventbus = new Vue();
2. 发送事件
  methods: {
     
    handleClick() {
     
      eventbus.$emit('message', 'hello world')
    }
  }
3. 接收事件
    const compb = {
     
      template: '

EventBus-componentb

'
, 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: '

EventBus-componentb

'
, mounted() { eventbus.$on('message', function (msg) { console.log('组件中获取的message===>',msg) }) eventbus.$off('message', function (msg) { console.log("侦听器已经移除") }) } } const compa = { template: `

EventBus-componenta

`
, components: { compb } } var vm = new Vue({ el: "#app", data: { username: 'china' }, components: { compa }, methods: { handleClick() { eventbus.$emit('message', 'hello world') } } }) </script> </body> </html>

【Vue.js】Vue组件间数据通信方式_第9张图片
缺点: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;

你可能感兴趣的:(笔记,vue,js,面试,经验分享)