侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 352 篇文章
  • 累计创建 135 个标签
  • 累计收到 10 条评论

目 录CONTENT

文章目录

vue组件之间的通信方式

孔子说JAVA
2022-10-03 / 0 评论 / 0 点赞 / 78 阅读 / 10,211 字 / 正在检测是否收录...
广告 广告

在vue中,每一个.vue文件,都被称之为组件,各个组件之间的信息传递称为组件通信。对于vue来说,组件之间的消息传递是非常重要的,按组件之间的关系分为:父子组件之间的通信、兄弟组件之间的通信、祖孙与后代之间的通信、非关系组件之间的通信。

12311256

1、组件间的通信方式

组件之间消息传递的常用方式如下:

  • props和$emit(常用)
  • attrsattrs和listeners
  • 中央事件总线(非父子组件间通信)
  • v-model
  • provide和inject
  • parentparent和children
  • vuex

2、示例代码

2.1 通过props传递

适用场景:父组件传递数据给子组件

使用方式:子组件设置props属性,定义接受父组件传递过来的参数,父组件在使用子组件标签中通过字面量来传递值。

// children.vue(子组件):

props:{
    // 字符串形式
    name:String // 接收的类型参数
    // 对象形式
    age:{
        type:Number, // 接收的类型为数值
        defaule:18, // 默认值为18
        require:true // age属性必须传递
    }
}

// Father.vue(父组件)

<!-- 组件使用v-bind传值 -->
<Children :name="jack" :age=18 />

注意:这里的父组件传值给子组件分为传引用和传值两种方式,传引用的时候改变引用关系,则子组件的也会变化(改变父组件中数组的引用时可以看到子组件的props数组随之改变,而子组件中绑定的数组却并没有随之改变)传值则不会。

props验证方式

props: {
    // fooA只接受数值类型的参数
    fooA: Number,
    // fooB可以接受字符串和数值类型的参数
    fooB: [String, Number],
    // fooC可以接受字符串类型的参数,并且这个参数必须传入
    msg: {
      type: String,
      required: true
    },
    // fooD接受数值类型的参数,如果不传入的话默认就是100
    fooD: {
      type: Number,
      default: 100
    },
    // fooE接受对象类型的参数
    fooE: {
      type: Object,
      // 当为对象类型设置默认值时必须使用函数返回
      default: function() {
        return { message: "Hello, world" };
      }
    },
    // fooF使用一个自定义的验证器
    fooF: {
      validator: function(value) {
        return value >= 0 && value <= 100;
      }
    },
  fooG: {
      type:Array,
      // 当为数组类型设置默认值时必须使用数组返回
      default: function() {
        return [];
      }
    },
}

props 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件修改父组件的状态。所以不应该在子组件中修改 props 中的值。这是我上次想修改父组件的值遇到的报错:

image-1664781489310

2.2 通过$emit触发自定义事件

适用场景:子组件传递数据给父组件。

使用方式:子组件通过emit触发自定义事件,emit触发自定义事件,emit第一个参数指父组件的监听事件名,后面的参数作为传递的数值,父组件绑定监听器获取到子组件传递过来的参数。

// Child.vue(子组件):
<button @click="cyy">按钮</button>
methods: {
    cyy() {
      this.$emit("zifu", "子组件向父组件传值", true);
    }
}

// 父组件:
<router  v-on:zifu="hehe"></router>
methods: {
   hehe: function(data, data2) {
     console.log(data, data2);
   }
}

image-1664782129678

2.3 兄弟组件中的传值

方法一:通过父组件中转

<div> //爸爸A
    <router></router>    //哥哥A1
    <vuex></vuex>    //弟弟A2
</div>

如果A1要向A2传值,可以用$emit传给A,A再使用v-bind传给A2。使用父组件做中转这里不举例了,只是把上面的子向父,父向子连起来用。

方法二:Bus中央事件总线

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。

新建一个Bus.js 页面

image-1664782453568

父组件代码:

<div> //爸爸A
    <router></router>    //哥哥A1
    <vuex></vuex>    //弟弟A2
</div>

哥哥A1组件:

<button @click="cyy">按钮</button> 点击按钮向弟弟A2传值脚本中:

// 脚本:

import Bus from "../api/Bus";   //注意引入
export default {
  data() {
    return {
      a: 1
    };
  },
  methods: {
    cyy() {
      Bus.$emit("zifu", this.a++, "子组件向兄弟组件传值");    //存 Bus.$emit
    }
  }
};

弟弟A2组件:

<p>接受兄弟A1传值=-------第{{ccc}}次,向{{ddd}}</p>

// 脚本中:

import Bus from "../api/Bus";
export default {
  data() {
    return {
      ccc: "",
      ddd: ""
    };
  },
  created() {
    Bus.$on("zifu", (val, val1) => {    //取  Bus.$on
      this.ccc = val;
      this.ddd = val1;
    });
  }
};

image-1664782655564

2.4 使用ref

ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例

使用方式:父组件在使用子组件时设置ref,父组件通过设置的ref来获取数据。

// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}

// 父组件
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.title);  // Vue.js
      comA.sayHello();  // 弹窗
    }
  }
</script>

2.5 EventBus 中央事件总线

使用场景:兄弟组件传值、非父子组件传值

使用方式:创建一个中央事件总线,相当于中转站,可以用它来传递事件和接收事件。一个组件通过emit触发自定义事件,emit触发自定义事件,emit第二个参数位传递的数值,另一个组件通过$on监听自定义事件。

Vue 官网介绍了非父子组件通信方法:

image-1664783520232

不过官网说的太简单了,新手看完估计还是一脸懵逼。还有这个空的 Vue 实例放到哪里合适也值得商榷。

image-1664783554654

假设 bb 组件里面有个按钮,点击按钮,把 123 传递给 aa 组件。

// 根组件(this.$root)

new Vue({
el: '#app',
 router,
 render: h => h(App),
 data: {
  // 空的实例放到根组件下,所有的子组件都能调用
Bus: new Vue()
}
})

bb 组件内调用事件触发

<button @click="submit">提交<button>

methods: {
  submit() {

   // 事件名字自定义,用不同的名字区别事件

   this.$root.$emit('eventName', 123)

  }

 }

aa 组件内调用事件接收

// 当前实例创建完成就监听这个事件

created(){

 this.$root.$on('eventName', value => {

  this.print(value)

 })

},

methods: {

 print(value) {

  console.log(value)

 }

},

// 在组件销毁时别忘了解除事件绑定

beforeDestroy() {

  this.$root.Bus.$off('eventName')

},

问题一:如果有多个组件组件需要通信,是不是要在根组件上多建几个 Bus?

  • 答:不需要的,只要保证事件名 (eventName)不一样就行了。

问题二:为什么要弄个 Bus?直接 this.on、this.emit 不更简单粗暴?

  • 答:按照文档上的说法是专门用一个空的 Vue 实例(Bus)来做中央事件总线更加清晰也易于管理。

2.6 parentparent或root、$children

使用方式:通过共同祖辈parent或者parent或者root搭建通信桥梁。parent/parent/children:访问父 / 子实例。

在组件内部可以直接通过子组件parent对父组件进行操作,父组件通过parent对父组件进行操作,父组件通过children对子组件进行操作.

Vue.component('child',{
    props:{
      value:String, //v-model会自动传递一个字段为value的prop属性
    },
    data(){
      return {
        mymessage:this.value
      }
    },
    methods:{
      changeValue(){
        this.$parent.message = this.mymessage;//通过如此调用可以改变父组件的值
      }
    },
    template:`
      <div>
        <input type="text" v-model="mymessage" @change="changeValue">
      </div>
  })
  Vue.component('parent',{
    template:`
      <div>
        <p>this is parent compoent!</p>
        <button @click="changeChildValue">test</button >
        <child></child>
      </div>
    `,
    methods:{
      changeChildValue(){
        this.$children[0].mymessage = 'hello';
      }
    },
    data(){
      return {
        message:'hello'
      }
    }
  })
  var app=new Vue({
    el:'#app',
    template:`
      <div>
        <parent></parent>
      </div>
    `
  })

2.7 attrsattrs与listeners

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法----attrs/attrs/listeners。

  • attrs:包含了父作用域中不被prop所识别(且获取)的特性绑定(classstyle除外)。当一个组件没有声明任何prop时,这里会包含所有父作用域的绑定(classstyle除外),并且可以通过vbind="attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
  • listeners:包含了父作用域中的(不含.native修饰器的)von事件监听器。它可以通过von="listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="listeners" 传入内部组件

使用场景:祖先传递数据给子孙

使用方式:设置批量向下传属性 $attrs 和 listeners,包含了父级作用域中不作为prop被识别(且获取)的特性绑定(classstyle除外),可以通过vbind="listeners, 包含了父级作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外),可以通过 `v-bind="attrs"` 传⼊内部组件。

// index.vue
<template>
  <div>
    <h2>浪里行舟</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端工匠"
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue"
    };
  }
};
</script>
// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  created() {
    console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <child-com3 v-bind="$attrs"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  created() {
    console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
  }
};
</script>
// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  }
};
</script>

12212311256

如上图所示attrs表示没有继承数据的对象,格式为属性名:属性值。Vue2.4提供了attrs表示没有继承数据的对象,格式为{属性名:属性值}。Vue2.4提供了attrs , $listeners 来传递数据与事件,跨级组件之间的通讯变得更简单。

简单来说:attrsattrs与listeners 是两个对象,attrs里存放的是父组件中绑定的非Props属性,attrs 里存放的是父组件中绑定的非 Props 属性,listeners里存放的是父组件中绑定的非原生事件。

2.8 provide 与 inject

Vue2.2.0新增API,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

  • 只要在父组件的生命周期内,子组件都可以调用。

使用方式:在祖先组件定义 provide 属性,返回传递的值, 在后代组件通过 inject 接收组件传递过来的值。

假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件

// A.vue
export default {
  provide: {
    name: '浪里行舟'
  }
}

后代组件:

// B.vue
export default {
  inject: ['name'],// 获取到祖先组件传递过来的值
  mounted () {
    console.log(this.name);  // 浪里行舟
  }
}

可以看到,在 A.vue 里,我们设置了一个 provide: name,值为 浪里行舟,它的作用就是将 name 这个变量提供给它的所有子组件。而在 B.vue 中,通过 inject 注入了从 A 组件中提供的 name 变量,那么在组件 B 中,就可以直接通过 this.name 访问这个变量了,它的值也是 浪里行舟。这就是 provide / inject API 最核心的用法。

需要注意的是:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的----vue官方文档 所以,上面 A.vue 的 name 如果改变了,B.vue 的 this.name 是不会改变的,仍然是 浪里行舟。

2.9 v-model

父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input’,val)自动修改v-model绑定的值

Vue.component('child',{
    props:{
      value:String, //v-model会自动传递一个字段为value的prop属性
    },
    data(){
      return {
        mymessage:this.value
      }
    },
    methods:{
      changeValue(){
        this.$emit('input',this.mymessage);//通过如此调用可以改变父组件上v-model绑定的值
      }
    },
    template:`
      <div>
        <input type="text" v-model="mymessage" @change="changeValue">
      </div>
  })
  Vue.component('parent',{
    template:`
      <div>
        <p>this is parent compoent!</p>
        <p>{{message}}</p>
        <child v-model="message"></child>
      </div>
    `,
    data(){
      return {
        message:'hello'
      }
    }
  })
  var app=new Vue({
    el:'#app',
    template:`
      <div>
        <parent></parent>
      </div>
    `
  })

2.10 vuex

使用场景:如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

使用方式:Vuex 作用相当于一个用来存储共享变量的容器,state 用来存放共享变量的地方,getter ,可以增加一个 getter 派生状态,(相当于 store 中的计算属性),用来获得共享变量的值,mutations 用来存放修改 state 的方法,actions 也是用来存放修改state的方法,不过 action 是在 mutations 的基础上进行。常用来做 一些异步操作。

122311256

3、总结

  1. 父子关系的组件数据传递选择props与emit进行传递,也可以选择ref。通过父链/子链也可以通信(emit进行传递,也可以选择ref。通过父链 / 子链也可以通信(parent / $children)。

  2. 兄弟关系的组件数据传递可以选择EventBus,也可以选择EventBus,也可以选择parent进行传递。Vuex也可以通信。

  3. 祖先与后代组件数据传递(跨级通信)可选择attrs与listeners或者provide与inject、$EventBus、Vuex。

  4. 复杂关系的组件数据传递可以通过vuex存放共享的变量。

0

评论区