浅谈vue组件间通讯

如何进行父子组件的通信?

​父组件通过import引入子组件,并在父组件中通过component属性注册,然后子组件就可以用标签的形式嵌进父组件了。

props

子组件通过props选项能够接收到来自父组件的数据。props是单向绑定的,即只能父组件向子组件传递数据,不能反向。并且传递方式也有两种:

  1. 静态传递:子组件通过props来声明一个自定义的属性,但这个属性是静态的,不能发生改变。通常用来传字符串。

  2. 动态传递:通过v-bind绑定props的自定义属性,传递过去可以是一个表达式、布尔值、对象等。

//父组件
<template>
  <div class="item">
    <TodoItem :msg="a+b"></TodoItem>
    <TodoItem v-bind:msg="msg"></TodoItem>
  </div>
</template>
<script>
  import TodoItem from "./components/TodoItem";
  export default{
    components:{
      TodoItem
    },
    data(){
      return {
        a:'我是组件1',
        b:'我是组件2',
        msg:'我是组件'+Math.random()
      }
    }
  }
</script>

<style scoped>
  .item{
    margin: 20px auto 20px 20px;
  }
</style>
//子组件
<template>
  <div>
      <p>{{msg}}</p>
  </div>
</template>

<script>
export default {
  name: "TodoItem",
  props:['msg']
}
</script>
$ref

​对于ref官方的解释是:ref 是被用来给元素或子组件注册引用信息的。引用信息将会注册在父组件的 $refs 对象上。

说这些可能比较抽象,简单来说就是:

  1. ref作用到子组件上时,指向的是子组件的实例,通过$refs可能获取到在子组件里定义的属性和方法

  2. ref作用到DOM元素上时,指向的就是DOM元素,通过$refs可能获取到该DOM的属性集合,轻松访问到DOM元素,作用与JQ选择器类似。

//父组件
<template>
  <div class="item">
    <TodoItem ref="msg"></TodoItem>
    <p ref="dom"></p>
  </div>
</template>
<script>
  import TodoItem from "./components/TodoItem.vue";
  export default{
    components:{
      TodoItem
    },
    mounted() {
      console.log(this.$refs)
      this.$refs.msg.getmsg("我是fw");
      this.$refs.dom.innerHTML="我是普通元素哦";
    }
  }
</script>

<style scoped>
  .item{
    margin: 20px auto 20px 20px;
  }
</style>
//子组件
<template>
  <div>
      <p>{{msg}}</p>
  </div>
</template>

<script>
export default {
  name: "TodoItem",
  data(){
    return{
      msg:''
    }
  },
  methods:{
    getmsg(s){
        this.msg=s;
    }
  }
}
</script>

在这里插入图片描述

props与$ref的区别:

​props着重于数据的传递,它并不能调用子组件的属性和方法。自定义标题和内容适合用props.
​ref着重于索引,主要用来调用子组件中的属性和方法,其实并不擅长数据传递。而且ref用在DOM元素的时候,有选择器的作用,这个功能比索引更常用到。

$emit

props和ref主要都是父组件向子组件通信,而$emit主要用于子组件向父组件通信。

vm.$emit(event,arg)

​$emit 绑定一个自定义事件event,当这个这个语句被执行到的时候,就会将参数arg传递给父组件,父组件通过@event监听并接收参数。

//父组件
<template>
  <div class="item">
    <TodoItem @getMsg="showMsg"></TodoItem>
    {{title}}
  </div>
</template>
<script>
  import TodoItem from "./components/HelloWorld.vue";
  export default{
    components:{
      TodoItem
    },
    data(){
      return{
        title:''
      }
    },
    methods:{
      showMsg(title){
        this.title=title;
      }
    }
  }
</script>

<style scoped>
  .item{
    margin: 20px auto 20px 20px;
  }
</style>
//子组件
<template>
  <div>
      <p>子组件</p>
  </div>
</template>

<script>
export default {
  mounted() {
    this.$emit('getMsg','我从子组件过来的')
  }
}
</script>
$parent - $children

可以直接获取组件的实例, 但是缺点就是只能父子之间, 并且直接操作组件实例不是一个好的选择。
$parent 就是父组件的实例对象,而 $children 就是当前实例的直接子组件实例了,不过这个属性值是数组类型(子组件实例组成的数组)的,且并不保证顺序,也不是响应式的。

// 定义 parent 组件
Vue.component('parent', {
  template: `
    <div>
      <p>this is parent component!</p>
      <button @click="changeChildValue">test</button>
      <child />
    </div>
  `,
  data() {
    return {
      message: 'hello'
    }
  },
  methods: {
    changeChildValue(){
      this.$children[0].mymessage = 'hello';
    }
  },
});

// 定义 child 组件
Vue.component('child', {
  template:`
    <div>
      <input type="text" v-model="mymessage" @change="changeValue" /> 
    </div>
  `,
  data() {
    return {
      mymessage: this.$parent.message
    }
  },
  methods: {
    changeValue(){
      this.$parent.message = this.mymessage;//通过如此调用可以改变父组件的值
    }
  },
});
    
const app = new Vue({
  el: '#app',
  template: `
    <div>
      <parent />
    </div>
  `
});

边界:

  • 最深层的子组件的 this.$children返回的是一个空数组
  • App.vue的this. $parent返回的是new Vue()

这种方式不推荐使用, 会造成项目结构混乱

祖孙组件间通信

事件总线

new一个空的vue实例, 利用它本身的 o n / on/ on/emit实现一个中心的状态管理, 接收和发布事件。

// 组件 A
Vue.component('A', {
  template: `
    <div>
      <p>this is A component!</p>
      <input type="text" v-model="mymessage" @input="passData(mymessage)"> 
    </div>
  `,
  data() {
    return {
      mymessage: 'hello brother1'
    }
  },
  methods: {
    passData(val) {
      //触发全局事件globalEvent
      this.$EventBus.$emit('globalEvent', val)
    }
  }
});

// 组件 B
Vue.component('B', {
  template:`
    <div>
      <p>this is B component!</p>
      <p>组件A 传递过来的数据:{{brothermessage}}</p>
    </div>
  `,
  data() {
    return {
      mymessage: 'hello brother2',
      brothermessage: ''
    }
  },
  mounted() {
    //绑定全局事件globalEvent
    this.$EventBus.$on('globalEvent', (val) => {
      this.brothermessage = val;
    });
  }
});

//定义中央事件总线
const EventBus = new Vue();

// 将中央事件总线赋值到 Vue.prototype 上,这样所有组件都能访问到了
Vue.prototype.$EventBus = EventBus;
const app = new Vue({
  el: '#app',
  template: `
    <div>
      <A />
      <B />
    </div>
  `
});

provide-inject

常用于组件和后代元素之间的通信,因为使用props的并且有好几级的跨越,那么每次都要声明接收并且传给子组件,很繁琐。
使用provide-inject的话,==不需要声明接收,也不需要向下传递,只要祖先元素provide数据即可,==可以是数值、函数,也可以提供多个,但是数据进行provider时,vue实例还未创建,所以this是undefined

其传递的对象和函数的话是响应式的,因为传递是其引用,如果是基本类型就不是响应式的

// 组件
export default {
  provide: {
    example: 'provide',
    example2: () => {
      console.log(this);
    },
  },
}
// 后代组件
export default {
  inject: ['example', 'example2'],
  mounted() {
    console.log(this.example) // provide
    this.example2() // undefined
  },
}
$attrs与 $listeners
  • $ attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定属性 (class和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。
  • $ listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件。
// 组件A
Vue.component('A', {
  template: `
    <div>
      <p>this is parent component!</p>
      <B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B>
    </div>
  `,
  data() {
    return {
      message: 'hello',
      messagec: 'hello c' //传递给c组件的数据
    }
  },
  methods: {
    // 执行B子组件触发的事件
    getChildData(val) {
      console.log(`这是来自B组件的数据:${val}`);
    },
    
    // 执行C子组件触发的事件
    getCData(val) {
      console.log(`这是来自C组件的数据:${val}`);
    }
  }
});

// 组件B
Vue.component('B', {
  template: `
    <div>
      <input type="text" v-model="mymessage" @input="passData(mymessage)"> 
      <!-- C组件中能直接触发 getCData 的原因在于:B组件调用 C组件时,使用 v-on 绑定了 $listeners 属性 -->
      <!-- 通过v-bind 绑定 $attrs 属性,C组件可以直接获取到 A组件中传递下来的 props(除了 B组件中 props声明的) -->
      <C v-bind="$attrs" v-on="$listeners"></C>
    </div>
  `,
  /**
   * 得到父组件传递过来的数据
   * 这里的定义最好是写成数据校验的形式,免得得到的数据是我们意料之外的
   *
   * props: {
   *   message: {
   *     type: String,
   *     default: ''
   *   }
   * }
   *
  */
  props: ['message'],
  data(){
    return {
      mymessage: this.message
    }
  },
  methods: {
    passData(val){
      //触发父组件中的事件
      this.$emit('getChildData', val)
    }
  }
});

// 组件C
Vue.component('C', {
  template: `
    <div>
      <input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)">
    </div>
  `,
  methods: {
    passCData(val) {
      // 触发父组件A中的事件
      this.$emit('getCData',val)
    }
  }
});
    
var app=new Vue({
  el:'#app',
  template: `
    <div>
      <A />
    </div>
  `
});

总结

父子通信

props,events,ref,parent/children,provide/inject,事件总线

兄弟或其他不相关的通信

事件总线

祖孙

props,events,attrs/listeners,provide/inject,事件总线

如果大家有什么意见或建议希望大家在评论区多多交流,谢谢😊
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值