Vue 组件间的通信

这一节咱们一块儿看看 vue 中组件间的数据是如何传递的。javascript

前面,咱们已经初步创建了 vue 组件化的思想,知道如何建立组件、引入组件以及如何在组件里的一些功能。接下来,咱们来学习怎么创建组件之间的链接,也就是组件的通信。直白一点说就是:在一个组件中作的操做如何更新到应用程序中的其余组件。css

这篇文章会从两个方便介绍 vue 组件间的通信:html

- 父子组件之间的通信
- 兄弟组件之间的通信

<!-- more -->vue

1、父子组件之间的通信

一、父组件向子组件通信

vue 中,将数据从父组件传递到子组件,能够用 props 来实现(这一点,在 React 中也是如此)。java

props 指的是从外部(父组件)设置的属性。同时,为了告诉 vue 子组件须要从自已的外部(父组件)接收数据,须要在子组件的 vue 对象中设置 props 属性。这个属性是一个 String 数组,每一个字符串表示一个能够从父组件接收的属性。react

咱们须要作两件事情:父组件使用属性绑定、子组件使用 props 对象接收。 看个例子:git

  • 父组件使用属性绑定 :
<template>
  <div>
    <ChildCom :list='list' :run='run' :home='this'></ChildCom>
  </div>
</template>

<script>
import ChildCom from './ChildCom';

export default {
  data() {
    return {
      list: ['我是父组件里面的数据', '我来自父组件'],
    };
  },
  components: {
    ChildCom,
  },
  methods: {
    run() {
      alert('我是父组件里面的方法'); // eslint-disable-line
    },
  },
};
</script>

咱们在父组件 ParentCom 里面引入了子组件 ChildCom 。为了将数据从父组件传到子组件,咱们在子组件 ChildCom 上绑定了几个属性:github

<childCom :list='list' :run='run' :home='this'></childCom>

绑定属性的时候,属性名前须要加冒号。这里咱们绑定了三个属性,父组件的 data 中的 listmethods 中的 run 方法以及指向父组件的 thisvuex

  • 子组件使用 props 对象接收 :

接下来,咱们建立一个 ChildCom 组件,经过子组件的 props 选项来得到父组件传过来的数据:数组

<template>
 <div>
   <div class='list'>
     <ul>
       <li v-for='item in list' :key='item'>{{ item }}</li>
     </ul>
   </div>
   <div class='buttons'>
     <button @click='run'>执行父组件的方法</button>
     <button @click='getParent()'>获取父组件的数据和方法</button>
   </div>
 </div>
</template>

<script>
export default {
 methods: {
   getParent() {
     alert(this.home); // eslint-disable-line
     alert(this.home.list); // eslint-disable-line
     alert(this.home.run); // eslint-disable-line
   },
 },
 props: ['list', 'run', 'home'],
};
</script>

<style lang="postcss" scoped>
.list {
 margin-bottom: 10px;
}
li {
 margin: 10px 0;
 list-style: none;
}
button {
 padding: 6px;
 background-color: #35b880;
 border: none;
 color: white;
 font-size: 16px;
 margin: 5px;
}
</style>

子组件的 props 中接收了父组件传递下来的属性。须要注意的是,props 字符串数组中的值(prop)要和在父组件中为子组件绑定属性的属性名保持一致。

这里咱们加了一些样式,在 App.vue 中引入父组件 ParentCom ,打开浏览器会看到:

父组件向子组件传递

这样,在子组件中就拿到了父组件传递下来的数据和方法以及父组件自己,点击按钮就能够查看到父组件传递给子组件的数据。

二、子组件向父组件通信

前面咱们知道了父组件如何向子组件通信,那子组件如何向父组件通信呢?这里介绍两种方式:

  • 子组件触发事件,父组件监听事件并做出数据改变
  • 父组件将变动数据的方法以 props 的形式传给子组件(借鉴 react 的父子通信方式)
2.1 监听事件

首先在子组件 ChildCom<template> 中添加一个新的标签 button。在这个 button 上添加一个click 事件:

<div class='buttons'>
  <!-- add this -->
  <button @click='submit("我是子组件传递给父组件的数据")'>子组件触发更改父组件的数据</button>
</div>

当咱们点击这个按钮的时候,想要执行 submit 方法,咱们在子组件的 <script> 中添加这个方法:

methods: {
  getParent() {
    alert(this.home); // eslint-disable-line
    alert(this.home.list); // eslint-disable-line
    alert(this.home.run); // eslint-disable-line
    alert(this.home.appendToList); // eslint-disable-line
  },
  // add this
  submit(text) {
    this.$emit('addItem', text);
  },
},

触发事件时发出($emit)自定义的事件: addItem ,这里咱们也给 addItem 事件传递了一个 text 参数。这样就完成了子组件发出自定义事件的过程。

接下来须要在父组件中监听子组件传递的自定义事件 addItem 。怎么作呢?

在父组件中给子组件绑定监听子组件中自定义的事件的方法。这就意味着咱们也须要在父组件中定义这个方法。看看代码:

<template>
  <div>
    <ChildCom :list='list' :run='run' :home='this' @addItem='addItem'></ChildCom>
  </div>
</template>

<script>
import ChildCom from './ChildCom';

export default {
  data() {
    return {
      list: ['我是父组件里面的数据', '我来自父组件'],
    };
  },
  components: {
    ChildCom,
  },
  methods: {
    run() {
      alert('我是父组件里面的方法'); // eslint-disable-line
    },
    addItem(item) {
      this.list.push(item);
    },
  },
};
</script>

在子组件上绑定监听子组件中自定义事件的方法须要使用 @ 符号,在 methods 中添加了 addItem 方法。这时候,咱们打开浏览器,点击第三个按钮,就会看到子组件向父组件传递的数据了。

子组件向父组件传递

2.2 传递 props

传递 props 的意思是说在父组件里面定义改变父组件数据的方法,而后经过 props 传递给子组件,这样子组件就能够触发执行从父组件传递下来的方法,达到更改父组件数据的目的。这种方法借鉴了 React 中组件通信的方式。看看代码:

咱们依旧使用上面的代码,在 ParentCom 组件中将 addItem 方法传递给子组件:

<ChildCom :list='list' :run='run' :home='this' @addItem='addItem' :addItem='addItem'></ChildCom>

在子组件 ChildCom 中添加一个 button ,在它的点击事件中执行父组件的 addItem 方法,因此,咱们也须要在子组件的 props 选项中把 addItem 方法添加进去:

<template>
  <div>
    <div class='list'>
      <ul>
        <li v-for='item in list' :key='item'>{{ item }}</li>
      </ul>
    </div>
    <div class='buttons'>
      <button @click='run'>执行父组件的方法</button>
      <button @click='getParent()'>获取父组件的数据和方法</button>
      <button @click='submit("我是子组件传递给父组件的数据")'>子组件触发更改父组件的数据</button>
      <!-- add this -->
      <button @click='addItem("我是经过子组件props方式传递给父组件的数据")'>子组件触发更改父组件的数据-2</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  methods: {
    getParent() {
      alert(this.home); // eslint-disable-line
      alert(this.home.list); // eslint-disable-line
      alert(this.home.run); // eslint-disable-line
      alert(this.home.appendToList); // eslint-disable-line
    },
    submit(text) {
      this.$emit('addItem', text);
    },
  },
  // add this
  props: ['list', 'run', 'home', 'addItem'],
};
</script>

打开浏览器,点击 button :

子组件向父组件传递

2、兄弟组件之间的通信

vue 中实现兄弟组件间的通信主要有两种方法:经过父组件进行兄弟组件之间通信、经过 EventHub 进行兄弟组件间通信。为了避免和上面讲述的父子组件之间通信的代码混淆,这里咱们从新新建组件来演示:

  • 父组件: ParentCard
  • 两个兄弟组件:BrotherCardSisterCard

一、经过父组件进行兄弟组件之间通信

简单来讲,就是让兄弟组件经过一个共同父组件进行通信。

首先建立父组件 ParentCard

<template>
  <div class='container'>
    <h2>父组件</h2>
    <button @click='stopCommunicate' v-if='showButton'>中止通信</button>
    <div class='card-body'>
      <brother-card :messageSon='messageson' @brotherSaid='messageDaughter' class='card-brother'></brother-card>
      <sister-card :messageDaughter='messagedaughter' @sisterSaid='messageSon' class='card-sister'></sister-card>
    </div>
  </div>
</template>

<script>
import BrotherCard from './BrotherCard';
import SisterCard from './SisterCard';

export default {
  name: 'ParentCard',
  data() {
    return {
      messagedaughter: '',
      messageson: '',
    };
  },
  components: { BrotherCard, SisterCard },
  methods: {
    messageDaughter(message) {
      this.messagedaughter = message;
    },
    messageSon(message) {
      this.messageson = message;
    },
    showButton() {
      return this.messagedaughter && this.messageson;
    },
    stopCommunicate() {
      this.messagedaughter = '';
      this.messageson = '';
    },
  },
};
</script>

<style scoped>
.container {
  width: 70%;
  margin: 10px auto;
  border-radius: 10px;
  box-shadow: 1px 1px 1px 1px rgba(50, 50, 93, 0.1),
    0 5px 15px rgba(0, 0, 0, 0.07) !important;
  padding: 30px;
}
.card-body {
  display: flex;
  justify-content: center;
}
.card-brother,
.card-sister {
  margin: 0 50px;
}
</style>

建立 BrotherCard 组件:

<template>
  <div>
    <div>
      <p>我是子组件:Brother</p>
      <button @click='messageSister'>给妹妹发消息</button>
      <div v-if='messageSon' v-html='messageSon'></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'BrotherCard',
  props: ['messageSon'],
  methods: {
    messageSister() {
      this.$emit('brotherSaid', 'Hi,妹妹');
    },
  },
};
</script>

建立 SisterCard 组件:

<template>
  <div>
    <div>
      <p>我是子组件:Sister</p>
      <button @click='messageBrother'>给哥哥发消息</button>
      <div v-if='messageDaughter' v-html='messageDaughter'></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'SisterCard',
  props: ['messageDaughter'],
  methods: {
    messageBrother() {
      this.$emit('sisterSaid', 'Hi,哥哥');
    },
  },
};
</script>

结果以下:

兄弟组件之间的通讯

在学习完父子组件之间的通信方法以后,经过父组件进行兄弟组件的通信就很简单了,其实就是把兄弟之间须要共享的数据提高至他们最近的父组件当中进行管理,将他们的父组件做为中间媒介(在 React 中把这种方式被称为状态提高)。

二、经过EventHub进行兄弟间组件通信

vue1.0 中,组件之间的通讯主要经过 $dispatch 沿着父链向上传播和经过 $broadcast 向下广播来实现。可是在 vue2.0$dispatch$broadcast 已经被弃用

vue 中也提供了相似 Redux 的组件通讯和状态管理方案:vuex。对于中大型的项目来讲,使用 vuex 是一个很好的选择。可是对于小型的项目来讲,若是一开始就引入了 vuex ,是彻底不必的。

vue 官方文档中也给出了$dispatch$broadcast 最简单的升级方式就是:经过使用事件中心,容许组件自由交流,不管组件处于组件树的哪一层。 vue 文档中把这个事件中心命名为 eventHub,也有不少其余教程中将其命名为 eventBus 。在本教程中,咱们统一命名为 eventHub

咱们一样基于上面的示例来作修改:ParentCard 组件包含了 SisterCardBrotherCard 两个子组件,并且这两个子组件是兄弟组件。

首先在 main.js 文件中定义一个新的 eventHub 对象(vue 实例 ):

import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

// add this
export const eventHub = new Vue(); // eslint-disable-line

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});

接着咱们要作的是让 eventHub 实例成为 BrotherCard 组件中发出事件的实例,使用 eventHub.$emit 来替代上例中的 this.$emit(由于 eventHub 是一个 vue 实例,因此它可使用 $emit 方法)。

<template>
  <div>
    <p>我是Brother组件</p>
    <button @click='messageSister'>给妹妹发消息</button>

    <div v-if='fromSister' v-html='fromSister'></div>
  </div>
</template>

<script>
import { eventHub } from '../../main';

export default {
  name: 'BrotherCard',
  data: () => ({
    fromSister: '',
  }),
  methods: {
    messageSister() {
      eventHub.$emit('brotherSaid', 'Hi,妹妹');
    },
  },
  /* eslint-disable */
  created() {
    eventHub.$on('sisterSaid', message => {
      this.fromSister = message;
    });
  },
};
</script>

引入 main.js,而且将 created() 生命周期钩子添加到 BrotherCard 组件中。在 created() 钩子函数中添加 eventHub 启动自定义事件的监听器,监听 sisterSaid 这个动做。

接下来咱们改造下 SisterCard 组件,和 BrotherCard 组件的改造是同样的:

<template>
  <div>
    <p>我是Sister组件</p>
    <button @click='messageBrother' class='btn'>给哥哥发消息</button>
    <div v-if='fromBrother' v-html='fromBrother'></div>
  </div>
</template>

<script>
import { eventHub } from '../../main';

export default {
  name: 'SisterCard',
  data: () => ({
    fromBrother: '',
  }),
  methods: {
    messageBrother() {
      eventHub.$emit('sisterSaid', 'Hi,哥哥');
    },
  },
  /* eslint-disable */
  created() {
    eventHub.$on('brotherSaid', message => {
      this.fromBrother = message;
    });
  },
};
</script>

这时候,咱们就不用在父组件 ParentCard 作任何操做了:

<template>
  <div class='container'>
    <h2>父组件</h2>
    <div class='card-body'>
      <brother-card class='card-brother'></brother-card>
      <sister-card class='card-sister'></sister-card>
    </div>
  </div>
</template>

<script>
import BrotherCard from './BrotherCard';
import SisterCard from './SisterCard';

export default {
  name: 'ParentCard',
  components: {
    'brother-card': BrotherCard,
    'sister-card': SisterCard,
  },
};
</script>

打开浏览器,能够看到这样也实现了兄弟组件之间的通信。

3、全局模式

这里的全局模式指的是建立全局变量和全局方法,让其余组件之间共享数据存储的模式。咱们看看怎么操做:

先建立一个 store.js ,在这个 JS 文件里建立全局的变量和方法:

const store = {
  state: { numbers: [1, 2, 3] },
  addNumber(newNumber) {
    this.state.numbers.push(newNumber);
  },
};

export default store;

storestate 中存放了一个 numbers 数组和一个 addNumber 方法。接下来咱们建立两个组件:

  • NumberDisplay 组件:用来显示来自 storenumbers 数组
  • NumberSubmit 组件:容许用户向数据数组中添加一个新的数字

建立 NumberDisplay 组件:

<template>
  <div>
    <h2>{{ storeState.numbers }}</h2>
  </div>
</template>

<script>
import store from './store';

export default {
  name: 'NumberDisplay',
  data() {
    return {
      storeState: store.state,
    };
  },
};
</script>

建立 NumberSubmit 组件:

<template>
  <div class='form'>
    <input v-model='numberInput' type='number'>
    <button @click='addNumber(numberInput)'>Add new number</button>
  </div>
</template>

<script>
import store from './store';

export default {
  name: 'NumberSubmit',
  data() {
    return {
      numberInput: 0,
    };
  },
  methods: {
    addNumber(numberInput) {
      store.addNumber(Number(numberInput));
    },
  },
};
</script>

接着在 GlobalMode.vue 中引用刚才建立的组件:

<template>
  <div>
    <NumberDisplay/>
    <NumberSubmit/>
  </div>
</template>

<script>
import NumberDisplay from '../components/pass-data-3/NumberDisplay';
import NumberSubmit from '../components/pass-data-3/NumberSubmit';

export default {
  name: 'GlobalMode',
  components: { NumberDisplay, NumberSubmit },
};
</script>

效果以下:

全局模式

能够看到,咱们使用这种方式也能够实现组件间的通信。

4、总结

最后,咱们画个图总结一下 Vue 组件间的通信:

Vue组件间的通信

本节内容代码地址: https://github.com/IDeepspace...
欢迎关注个人博客: https://togoblog.cn/
相关文章
相关标签/搜索