Vue: 六种组件之间的数据通信方式

270 阅读3分钟
方式一:props、emit
props:作为数据传递的参数方式,作为子组件的属性,通过属性绑定接收来自父组件的数据

$emit:子组件向父组件传值(通过事件形式),子组件通过 $emit 事件向父组件发送消息,将自己的数据传递给父组件
//代码如下
<template>
  <div>
    <children-component :name="value" @click="handleClick"></children-component>
  </div>
</template>

<script>

  //子组件
  const childrenComponent = {
    props: {
      name: {
        type: String,
        default: '',
        require: true, //声明该属性,则props是必传参数,但一般不和default同时使用,一旦有默认值,这个参数设置的意义不大
      }
    },
    template: `
      <div style="padding: 30px" @click="handleClick">
        {{name}}
      </div>`,
    data() {
      return {
        count: 0
      }
    },
    methods: {
      handleClick() {
        this.count += 1;
        this.$emit('click', this.count)
      }
    }
  }

  export default {
    name: "sendData",
    components: {
      childrenComponent
    },
    data() {
      return {
        value: '我是父组件要传递给子组件的数据'
      }
    },
    methods: {
      handleClick(e) {
        console.log(`打印子组件传递过来的数据:${e}`);
      }
    }
  }
</script>

<style scoped>

</style>

方式二:on、emit
总线数据传递: 通过创建一个Vue实例,通过它来触发以及监听事件,实现任意组件之间的通信,包括父子组件、兄弟组件、隔代组件等
//方法1.通过props实现总线对象的传递
//方法2. 通过prototype进行总线对象的挂载,绑定到每个对象上(也可以直接访问$bus数据)

<template>
  <div>
    <children-one :bus-event="busEvent"></children-one>
    <children-two :bus-event="busEvent"></children-two>
    <children-third :bus-event="busEvent"></children-third>
  </div>
</template>

<script>
  //总线机制数据传递
  import Vue from 'vue'

  const busEvent = new Vue();
  // Vue.prototype.$busEvent = busEvent; 

  //子组件
  const childrenOne = {
    props: {
      busEvent: Object
    },
    template: `
      <div style="padding: 30px">
        我的名字是: {{name}}
        <button @click="handleSend">点击我向组件三告知我的名字</button>
      </div>`,
    data() {
      return {
        name: '组件1'
      }
    },
    methods: {
      handleSend() {
        this.$busEvent.$emit('child-one-msg', this.name);
      }
    }
  }

  //子组件2
  const childrenTwo = {
    props: {
      busEvent: Object
    },
    template: `
      <div style="padding: 30px">
        我是子组件2,我的年龄是{{age}}岁
        <button @click="handleSend">点击我向组件三告知我的年龄</button>
      </div>`,
    data() {
      return {
        age: 0
      }
    },
    methods: {
      handleSend() {
        this.age +=1;
        this.$busEvent.$emit('child-two-msg', this.age);
      }
    }
  }

  const childrenThird = {
    props: {
      busEvent: Object
    },
    template: `
      <div style="padding: 30px">
        我是组件三: 老大是{{childOneName}}、老二{{childTwoAge}}岁
      </div>`,
    data() {
      return {
        childOneName: '',
        childTwoAge: ''
      }
    },
    mounted() {
      this.$busEvent.$on('child-one-msg', (name) => {
        this.childOneName = name;
      })

      this.$busEvent.$on('child-two-msg', (age) => {
        this.childTwoAge = age;
      })
    }

  }

  export default {
    name: "sendData",
    components: {
      childrenOne,
      childrenTwo,
      childrenThird
    },
    data() {
      return {
        busEvent: busEvent
      }
    },
    methods: {
      handleClick(e) {
        console.log(`打印子组件传递过来的数据:${e}`);
      }
    }
  }
</script>
方式三: Vuex(全局仓库)
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

以一个单向数据流的方式,通过mutations来修改state的数据。最有根据state的数据变化,渲染页面。
//main.js

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

new Vue({
  store,
  render: h => h(App),
}).$mount('#app');

//store.js

  import Vue from 'vue'
  import Vuex from 'vuex'

  Vue.use(Vuex)

  export default new Vuex({
    state: {
      count: 1
    },
    getters: {
      doubleCount: state => state.count * 2
    },
    mutations: { //同步数据修改
      addCount(state) {
        state.count++;
      },
      reduceCount(state) {
        state.count--;
      }
    },
    actions: {
      async_Add(state) { //异步修改
        this.commit('addCount')
      },
      async_Reduce(state) {
        this.commit('reduceCount')
      }
    }

  })

//子组件中
<template>
  <div class="container">
    <h3>测试Vuex数据传递</h3>
    <p>我的count:{{count}}</p>
    <p>doubleCount:{{doubleCount}}</p>
    <button @click="add()">增加</button>
    <button @click="reduce()">减少</button>
  </div>
</template>

<script>
  import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'

  export default {
    name: "vuexData",
    computed: {
      ...mapState(['count']),
      ...mapGetters(['doubleCount'])
    },
    methods: {
      ...mapMutations(['addCount', 'reduceCount']),
      ...mapActions({
        'add': 'async_Add',
        'reduce': 'async_Reduce'
      })
    }
  }
</script>

方式四:attrs、listeners (v2.4)
$attrs: 包含了父作用域中不作为Prop被识别(且获取)的特性绑定(class和style)除外,简而言之,pros的属性不存在$attrs中。

当一个组件没有声明任何 Prop 时,这里会包含所有父作用域的绑定 (Class 和 Style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用
$listeners: 包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

<template>
  <div class="container">
    <button style="background-color: lightgray" @click="reduce">减少</button>
    <child-one :aa="aa" :bb="bb" :cc="cc" :dd="dd" @click="reduce" v-on:reduce="reduce"></child-one>
  </div>
</template>

<script>

  const childThird = {
    props: ['dd'],
    template: `
      <div style="padding: 10px;background-color: orchid">
        <p>childThird:{{$attrs}}</p>

      </div>`,
  }

  const childTwo = {
    props: ['bb'],
    components: {childThird},
    template: `
      <div style="padding: 10px;background-color: orange">
        <div>
          <p>组件二bb:{{bb}}</p>
          <p>childTwo的$attrs:{{$attrs}}</p>
          <button @click="reduce2">组件2减dd数据</button>
        </div>
        <child-third v-bind="$attrs"></child-third>
      </div>`,
    methods: {
      reduce2() {
        this.$emit('reduce');
      }
    }
  }

  const childOne = {
    props: ['aa'],
    components: {
      childTwo
    },
    template: `
      <div style="padding: 10px;background-color: red">
        <div>
          <p>组件一aa:{{aa}}</p>
          <p>childOne的$attrs:{{$attrs}}</p>
          <button @click="reduce1">组件1减dd数据</button>
        </div>
        <child-two v-bind="$attrs" v-on="$listeners"></child-two>
      </div>`,
    methods: {
      reduce1() {
        this.$emit('reduce');
      }
    }
  }

  export default {
    name: "listenData",
    components: {
      childOne
    },
    data() {
      return {
        aa: 1,
        bb: 2,
        cc: 3,
        dd: 100
      }
    },
    methods: {
      reduce() {
        this.dd--;
      }
    }
  }
</script>

<style scoped>

</style>

方式五 Provide、inject (v2.2)
Provide和inject需要同时使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
简单来说,就是父组件通过 Provider 传入变量,任意子孙组件通过 Inject 来拿到变量
provide需要是一个函数,因为provide初始化的时候,对象data还没有初始化成功,所以需要是一个函数,本身不支持响应式数据,所以如果使用到data里面的数据的时候,需要用 Object.defineProperties关联绑定
<template>
  <div>
    <input type="text" v-model="value">
    <child-one></child-one>

  </div>
</template>

<script>
  const childOne = {
    template: `<div>this is childcomponent: {{data.value}}</div>`,
    inject: ['data'],
    mounted() {
      console.log(this.data.value);
    }
  }

  export default {
    name: "provideData",
    components: {
      childOne
    },
    data() {
      return {
        value: '我是父组件数据'
      }
    },

    provide() { //provide需要是一个函数,因为provide初始化的时候,对象data还没有初始化成功,所以需要是一个函数,
               // 本身不支持响应式数据,所以如果使用到data里面的数据的时候,需要用 Object.defineProperties关联绑定
      const data = {};
      Object.defineProperties(data, {
        value: {
          get: () => this.value,
          set: (v) => {
            console.log(v)
          },
          enumerable: true
        }
      })
      return {
        data
      }
    }
  }
</script>

<style scoped>

</style>

方式六:parent、children、ref
1. $parent / $children:指定已创建的实例之父实例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。

2. $refs 一个对象,持有注册过 ref 特性 的所有 DOM 元素和组件实例。ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件。
<template>
  <div class="container">
    <p>我的title:{{title}}</p>
    <p>我的name:{{name}}</p>
    <child-one ref="comp1"></child-one>
    <child-two ref="comp2"></child-two>
  </div>
</template>

<script>
  const childOne = {
    template: `
      <div>我的父组件是谁: {{content}}</div>`,
    data() {
      return {
        title: '我是子组件1',
        content: ''
      }
    },
    mounted() {
      this.content = this.$parent.content;
    },
    methods: {
      sayHello() {
        window.alert('hello')
      }
    }
  }
  const childTwo = {
    template: `
      <div>我是子组件2</div>`,
    data() {
      return {
        title:'我是子组件2'
      }
    }
  }


  export default {
    name: "parentData",
    components: {
      childOne,
      childTwo
    },
    data() {
      return {
        title: '',
        name: '',
        content: '我就是父组件'
      }
    },
    mounted() {
      const comp1 = this.$refs.comp1;
      this.title = comp1.title;
      comp1.sayHello();
      this.name = this.$children[1].title;
    }
  }
</script>

总结

组件间不同的使用场景可以分为3类,对应的通信方式如下:

 1. 父子通信: Props / $emit$emit / $on,Vuex,$attrs / ​$listeners,provide/inject,$parent / $children$refs
 
 2. 兄弟通信:$emit / $on,Vuex
 
 3. 隔代通信:$emit / ​$on,Vuex,provide / inject,$attrs / $listeners