精华内容
下载资源
问答
  • vue组件通信
    千次阅读
    2022-04-01 14:24:15

    vue是数据驱动视图更新的框架,所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢?

    首先我们需要知道在vue中组件之间存在什么样的关系, 才更容易理解他们的通信方式, 就好像过年回家,坐着一屋子的陌生人,相互之间怎么称呼,这时就需要先知道自己和他们之间是什么样的关系。

    vue组件中关系说明:


     

    如上图所示, A与B、A与C、B与D、C与E组件之间是父子关系; B与C之间是兄弟关系;A与D、A与E之间是隔代关系; D与E是堂兄关系(非直系亲属)
    针对以上关系我们归类为: 

    • 父子组件之间通信
    • 非父子组件之间通信(兄弟组件、隔代关系组件等)

    本文会介绍组件间通信的8种方式如下图目录所示:并介绍在不同的场景下如何选择有效方式实现的组件间通信方式,希望可以帮助小伙伴们更好理解组件间的通信。 

    目录

    一、 props / $emit

    二、 $children / $parent

    三、provide/ inject

    四、ref / refs

    五、eventBus

    六、Vuex 

    七、localStorage / sessionStorage

    八、$attrs与 $listeners


    一、 props / $emit

    父组件通过props的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。

    1. 父组件向子组件传值

    下面通过一个例子说明父组件如何向子组件传递数据:在子组件article.vue中如何获取父组件section.vue中的数据articles:['红楼梦', '西游记','三国演义']

    // section父组件
    <template>
      <div class="section">
        <com-article :articles="articleList"></com-article>
      </div>
    </template>
    
    <script>
    import comArticle from './test/article.vue'
    export default {
      name: 'HelloWorld',
      components: { comArticle },
      data() {
        return {
          articleList: ['红楼梦', '西游记', '三国演义']
        }
      }
    }
    </script>
    // 子组件 article.vue
    <template>
      <div>
        <span v-for="(item, index) in articles" :key="index">{{item}}</span>
      </div>
    </template>
    
    <script>
    export default {
      props: ['articles']
    }
    </script>

    总结: prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。

    2. 子组件向父组件传值

    对于$emit 我自己的理解是这样的: $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数。 通过一个例子,说明子组件如何向父组件传递数据。
    在上个例子的基础上, 点击页面渲染出来的ariticleitem, 父组件中显示在数组中的下标

    // 父组件中
    <template>
      <div class="section">
        <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
        <p>{{currentIndex}}</p>
      </div>
    </template>
    
    <script>
    import comArticle from './test/article.vue'
    export default {
      name: 'HelloWorld',
      components: { comArticle },
      data() {
        return {
          currentIndex: -1,
          articleList: ['红楼梦', '西游记', '三国演义']
        }
      },
      methods: {
        onEmitIndex(idx) {
          this.currentIndex = idx
        }
      }
    }
    </script>
    <template>
      <div>
        <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
      </div>
    </template>
    
    <script>
    export default {
      props: ['articles'],
      methods: {
        emitIndex(index) {
          this.$emit('onEmitIndex', index)
        }
      }
    }
    </script>

    二、 $children / $parent

    上面这张图片是vue官方的解释,通过$parent$children就可以访问组件的实例,拿到实例代表什么?代表可以访问此组件的所有方法和data。接下来就是怎么实现拿到指定组件的实例。 

    使用方法:

    // 父组件中
    <template>
      <div class="hello_world">
        <div>{{msg}}</div>
        <com-a></com-a>
        <button @click="changeA">点击改变子组件值</button>
      </div>
    </template>
    
    <script>
    import ComA from './test/comA.vue'
    export default {
      name: 'HelloWorld',
      components: { ComA },
      data() {
        return {
          msg: 'Welcome'
        }
      },
    
      methods: {
        changeA() {
          // 获取到子组件A
          this.$children[0].messageA = 'this is new value'
        }
      }
    }
    </script>
    // 子组件中
    <template>
      <div class="com_a">
        <span>{{messageA}}</span>
        <p>获取父组件的值为:  {{parentVal}}</p>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          messageA: 'this is old'
        }
      },
      computed:{
        parentVal(){
          return this.$parent.msg;
        }
      }
    }
    </script>

    要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent$children的值不一样,$children 的值是数组,而$parent是个对象

    总结: 

    上面两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍; 二者皆不能用于非父子组件之间的通信。

    三、provideinject

    概念:

    provideinject 是vue2.2.0新增的api, 简单来说就是父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。

    注意: 这里不论子组件嵌套有多深, 只要调用了inject 那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据

    举例验证

    接下来就用一个例子来验证上面的描述:
    假设有三个组件: A.vue、B.vue、C.vue 其中 C是B的子组件,B是A的子组件

    // A.vue
    
    <template>
      <div>
        <comB></comB>
      </div>
    </template>
    
    <script>
      import comB from '../components/test/comB.vue'
      export default {
        name: "A",
        provide: {
          for: "demo"
        },
        components:{
          comB
        }
      }
    </script>

    // B.vue
    
    <template>
      <div>
        {{demo}}
        <comC></comC>
      </div>
    </template>
    
    <script>
      import comC from '../components/test/comC.vue'
      export default {
        name: "B",
        inject: ['for'],
        data() {
          return {
            demo: this.for
          }
        },
        components: {
          comC
        }
      }
    </script>
    // C.vue
    <template>
      <div>
        {{demo}}
      </div>
    </template>
    
    <script>
      export default {
        name: "C",
        inject: ['for'],
        data() {
          return {
            demo: this.for
          }
        }
      }
    </script>

    四、ref / refs

    ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据, 我们看一个ref 来访问组件的例子:

    // 子组件 A.vue
    
    export default {
      data () {
        return {
          name: 'Vue.js'
        }
      },
      methods: {
        sayHello () {
          console.log('hello')
        }
      }
    }
    // 父组件 app.vue
    
    <template>
      <component-a ref="comA"></component-a>
    </template>
    <script>
      export default {
        mounted () {
          const comA = this.$refs.comA;
          console.log(comA.name);  // Vue.js
          comA.sayHello();  // hello
        }
      }
    </script>

    五、eventBus

    eventBus 又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。

    eventBus也有不方便之处, 当项目较大,就容易造成难以维护的灾难

    在Vue的项目中怎么使用eventBus来实现组件之间的数据通信呢?具体通过下面几个步骤

    1. 初始化

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

    // event-bus.js
    
    import Vue from 'vue'
    export const EventBus = new Vue()

    2. 发送事件

    假设你有两个组件: additionNum 和 showNum, 这两个组件可以是兄弟组件也可以是父子组件;这里我们以兄弟组件为例:

    <template>
      <div>
        <show-num-com></show-num-com>
        <addition-num-com></addition-num-com>
      </div>
    </template>
    
    <script>
    import showNumCom from './showNum.vue'
    import additionNumCom from './additionNum.vue'
    export default {
      components: { showNumCom, additionNumCom }
    }
    </script>
    // addtionNum.vue 中发送事件
    
    <template>
      <div>
        <button @click="additionHandle">+加法器</button>    
      </div>
    </template>
    
    <script>
    import {EventBus} from './event-bus.js'
    console.log(EventBus)
    export default {
      data(){
        return{
          num:1
        }
      },
    
      methods:{
        additionHandle(){
          EventBus.$emit('addition', {
            num:this.num++
          })
        }
      }
    }
    </script>

     3. 接收事件

    // showNum.vue 中接收事件
    
    <template>
      <div>计算和: {{count}}</div>
    </template>
    
    <script>
    import { EventBus } from './event-bus.js'
    export default {
      data() {
        return {
          count: 0
        }
      },
    
      mounted() {
        EventBus.$on('addition', param => {
          this.count = this.count + param.num;
        })
      }
    }
    </script>

    这样就实现了在组件addtionNum.vue中点击相加按钮, 在showNum.vue中利用传递来的 num 展示求和的结果.

    4. 移除事件监听者

    如果想移除事件的监听, 可以像下面这样操作:

    import { eventBus } from 'event-bus.js'
    EventBus.$off('addition', {})

    六、Vuex 

    1. Vuex介绍

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化.
    Vuex 解决了多个视图依赖于同一状态来自不同视图的行为需要变更同一状态的问题,将开发者的精力聚焦于数据的更新而不是数据在组件之间的传递上

    2. Vuex各个模块

    • state:用于数据的存储,是store中的唯一数据源

    • getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算

    • mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件

    • actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作

    • modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

    3. Vuex实例应用

    // 父组件
    
    <template>
      <div id="app">
        <ChildA/>
        <ChildB/>
      </div>
    </template>
    
    <script>
      import ChildA from './components/ChildA' // 导入A组件
      import ChildB from './components/ChildB' // 导入B组件
    
      export default {
        name: 'App',
        components: {ChildA, ChildB} // 注册A、B组件
      }
    </script>
    // 子组件childA
    
    <template>
      <div id="childA">
        <h1>我是A组件</h1>
        <button @click="transform">点我让B组件接收到数据</button>
        <p>因为你点了B,所以我的信息发生了变化:{{BMessage}}</p>
      </div>
    </template>
    
    <script>
      export default {
        data() {
          return {
            AMessage: 'Hello,B组件,我是A组件'
          }
        },
        computed: {
          BMessage() {
            // 这里存储从store里获取的B组件的数据
            return this.$store.state.BMsg
          }
        },
        methods: {
          transform() {
            // 触发receiveAMsg,将A组件的数据存放到store里去
            this.$store.commit('receiveAMsg', {
              AMsg: this.AMessage
            })
          }
        }
      }
    </script>
    // 子组件 childB
    
    <template>
      <div id="childB">
        <h1>我是B组件</h1>
        <button @click="transform">点我让A组件接收到数据</button>
        <p>因为你点了A,所以我的信息发生了变化:{{AMessage}}</p>
      </div>
    </template>
    
    <script>
      export default {
        data() {
          return {
            BMessage: 'Hello,A组件,我是B组件'
          }
        },
        computed: {
          AMessage() {
            // 这里存储从store里获取的A组件的数据
            return this.$store.state.AMsg
          }
        },
        methods: {
          transform() {
            // 触发receiveBMsg,将B组件的数据存放到store里去
            this.$store.commit('receiveBMsg', {
              BMsg: this.BMessage
            })
          }
        }
      }
    </script>

    vuex的store,js

    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    const state = {
      // 初始化A和B组件的数据,等待获取
      AMsg: '',
      BMsg: ''
    }
    
    const mutations = {
      receiveAMsg(state, payload) {
        // 将A组件的数据存放于state
        state.AMsg = payload.AMsg
      },
      receiveBMsg(state, payload) {
        // 将B组件的数据存放于state
        state.BMsg = payload.BMsg
      }
    }
    
    export default new Vuex.Store({
      state,
      mutations
    })

    七、localStorage / sessionStorage

    这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。

    通过window.localStorage.getItem(key)获取数据

    通过window.localStorage.setItem(key,value)存储数据

    注意用JSON.parse() / JSON.stringify() 做数据格式转换
    localStorage / sessionStorage可以结合vuex, 实现数据的持久保存,同时使用vuex解决数据和状态混乱问题.

    八、$attrs$listeners


    现在我们来讨论一种情况, 我们一开始给出的组件关系图中A组件与D组件是隔代关系, 那它们之前进行通信有哪些方式呢?

    • 使用props绑定来进行一级一级的信息传递, 如果D组件中状态改变需要传递数据给A, 使用事件系统一级级往上传递

    • 使用eventBus,这种情况下还是比较适合使用, 但是碰到多人合作开发时, 代码维护性较低, 可读性也低

    • 使用Vuex来进行数据管理, 但是如果仅仅是传递数据, 而不做中间处理,使用Vuex处理感觉有点大材小用了.

    vue2.4中,为了解决该需求,引入了$attrs$listeners , 新增了inheritAttrs 选项。 在版本2.4以前,默认情况下,父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外),将会“回退”且作为普通的HTML特性应用在子组件的根元素上。接下来看一个跨级通信的例子:

    // app.vue
    // index.vue
    
    <template>
      <div>
        <child-com1
          :name="name"
          :age="age"
          :gender="gender"
          :height="height"
          title="程序员成长指北"
        ></child-com1>
      </div>
    </template>
    <script>
    const childCom1 = () => import("./childCom1.vue");
    export default {
      components: { childCom1 },
      data() {
        return {
          name: "zhang",
          age: "18",
          gender: "女",
          height: "158"
        };
      }
    };
    </script>
    // childCom1.vue
    
    <template class="border">
      <div>
        <p>name: {{ name}}</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: {
        name: String // name作为props属性绑定
      },
      created() {
        console.log(this.$attrs);
         // { "age": "18", "gender": "女", "height": "158", "title": "程序员成长指北" }
      }
    };
    </script>

    // childCom2.vue
    
    <template>
      <div class="border">
        <p>age: {{ age}}</p>
        <p>childCom2: {{ $attrs }}</p>
      </div>
    </template>
    <script>
    
    export default {
      inheritAttrs: false,
      props: {
        age: String
      },
      created() {
        console.log(this.$attrs); 
        // { "gender": "女", "height": "158", "title": "程序员成长指北" }
      }
    };
    </script>

    如有更好的方法,请补充~

    更多相关内容
  • 本文实例讲述了vue组件通信传值操作。分享给大家供大家参考,具体如下: 父子组件通信: 子组件 <h3 click=alerrt> 我是子组件一 <span>{{parentMessage}} [removed] export default{ props: ['...
  • 本篇文章主要介绍了Vue组件通信之Bus的具体使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性案例2.2:平行组件之间通信:Student2组件内调用Student1组件方法修改Student1组件属性三、html项目页面实现平行组件通信案例本.

    在这里插入图片描述

    文章目录

    一、案例概述

    说明点1:我使用vue-cli也就是脚手架创建的vue项目,即模拟真实项目创建School.vue组件文件,通过组建引入方式练习,而不是在html页面中引入vue.js练习。

    说明点2:vue我目前只学习到组件,所以通信方式较单一,如果想多方式实现请查看下面别人的文章 vue组件间通信六种方式(完整版)

    说明点3:该案例练习包含两大分类

    第一类:父子组件之间通信

    在这里插入图片描述

    • 案例1.1:父组件向子组件传值(或者叫:子组件使用父组件属性),采用v-bind方式实现
    • 案例1.2:子组件向父组件传值(或者叫:子组件调用父组件方法),修改父组件属性,采用$emit和v-on(或者叫@自定义事件)方式实现
    • 案例1.3:父组件调用子组件方法,修改子组件属性值,采用$refs方式实现
    • 案例1.4:父组件直接修改子组件属性值,采用$refs方式实现

    第二类:平行组件间之间通信

    注意点1:默认情况两个子组件之间是无法直接通信的,所以需要构建父组件,通过把父组件当做一个传递桥梁进而实现平行组件间之间通信。(即“子组件student1”把数据传递给“父组件school”,然后“父组件school”再把数据传递给“子组件student2”使用)

    • 案例2.1:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性,采用v-bind方式实现
    • 案例2.2:平行组件之间通信:Student2组件内调用Student1组件方法修改Student1组件属性,采用$refs和v-on(或者叫@自定义事件)方式实现
    • 案例2.3:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性,采用“全局事件总线”实现
    • 案例2.4:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性,采用“消息订阅与发布”实现
    • 案例2.5:平行组件之间通信:,Student1组件和Student2组件共同读取vuex共享数据,并实现修改vuex数据,采用“vuex方式”实现
      在这里插入图片描述

    第三类:provide/inject,嵌套父子组建通信

    • 案例3.1:嵌套父子组建通信,实现父组件一次性给所有子孙组件传值,采用“provide/inject”方式

    第四类:$parent / $children与 ref,获取父 / 子组件实例

    • 案例4.1:获取父 / 子组件实例,采用“$parent / $children与 ref”方式

    二、代码

    准备工作:

    模拟真实项目目录准备如下:图1红框部分为模拟项目使用的必须文件,图2整体流程图如下,
    图1

    图1

    在这里插入图片描述

    图2

    index.html 创建首页

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <title><%= htmlWebpackPlugin.options.title %></title>
      </head>
      <body>
        <noscript>
          <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
      </body>
    </html>
    
    

    App.vue 创建App组件,它是所有组件的根部爸爸

    <template>
    	  <div>
    	    <img alt="Vue logo" src="./assets/logo.png"><hr>
    	    <School></School>
    	  </div>
    </template>
    
    <script>
    import School from './components/School'
    
    export default {
    	  name: 'App',
    	  components: {
    	    School
    	  }
    }
    </script>
    
    <style>
    #app {
    	  font-family: Avenir, Helvetica, Arial, sans-serif;
    	  -webkit-font-smoothing: antialiased;
    	  -moz-osx-font-smoothing: grayscale;
    	  text-align: center;
    	  color: #2c3e50;
    	  margin-top: 60px;
    }
    </style>
    
    

    main.js 程序主入口,用来绑定id=App的div标签

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    new Vue({
    	  render: h => h(App),
    	  template:`<App></App>`
    }).$mount('#app')
    
    

    案例1.1:父组件向子组件传值(或者叫:子组件使用父组件属性),采用v-bind方式实现

    采用v-bind方式实现

    注意点1:
    语法:父组件配置v:bind传值,子组件配置props:{}接收
    大白话讲:父组件通过使用子组件标签,在内部配置v:bind指令传属性,也就是:number=“count” :ids="arr"等等,而子组件通过配置props:{}来接收父组件传递过来的属性

    父组件School.vue

    <template>
      <div>  
        <student1 :number="count" :ids="arr" :person="p" ></student1>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    
    export default {
      name: "School",
      components: {Student1},
      data() {
        return {
          count: 5,
          arr: [1, 2, 3],
          p: {username: "zhangsan", age: 23}
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student1.vue

    <template>
      <div>
        <h1>案例1.1:父组件向子组件传值(或者叫:子组件使用父组件属性):</h1>
        <p>这是学生1子组件</p>
        <p>学生1子组件的属性size:{{size}}</p>
        <p>父组件传递过来的属性值:{{number}}--{{ids}}--{{person}}</p>    
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      data() {
        return {
          size:110
        }
      },
      //给组件添加属性 -》 是组件用来接收父组件数据对象的
      props: {
        //父传子属性:普通属性number
        number: null,
        //父传子属性:数组属性ids
        ids: [],
        //父传子属性:对象属性person
        person: {} 
      }
    }
    </script>
    
    <style scoped></style>
    

    页面展示效果

    在这里插入图片描述

    案例1.2:子组件向父组件传值(或者叫:子组件调用父组件方法),修改父组件属性,采用$emit和v-on(或者叫@自定义事件)方式实现

    采用$emit和v-on(或者叫@自定义事件)方式实现

    注意点1:

    语法:子组件配置 $emit传值, 父组件配置 v-on 接收。
    大白话讲:父组件使用子组件标签时配置v-on指令接收子组件传递过来的属性,也就是@receivePCount="add(arguments)",而子组件配置this.$emit()来触发调用函数并传参给父组件
    

    注意点2:

    问题:子组件调用父组件方法时传参,父组件如何接收到参数值?

    1)如果只传递一个参数,比如:this.$emit('update-count', "你是谁?"); 
    那么子组件标签形参可不带参数或者形参使用$event
    比如<child v-on:update-count="changeCount"></child> 
    或者<child v-on:update-count="changeCount($event)"></child>
    那么父组件(vue实例)方法中通过value即可接收参数比如:  changeCount:function(value)
    
    2)如果传递多个参数,比如: this.$emit('update-count', "ldz",29);,
    那么子组件标签形参请使用arguments
    <child v-on:update-count="changeCount(arguments)"></child>
    那么父组件(vue实例)通过value[index]即可接收参数,比如:
    changeCount:function(value){
    console.log("@" + value[0]);
    console.log("@" + value[1]);
    

    父组件School.vue

    <template>
      <div>
        <p>父组件属性count:{{count}}--msg:{{msg}}</p>
        <student1  @receivePCount="add(arguments)" ></student1>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    
    export default {
      name: "School",
      components: {Student1},
      data() {
        return {
          count: 5,
          msg:""
        }
      },
      methods: {
        //子组件向父组件通信
        add(args){
          this.count += args[0];
          this.msg += args[1];
        }    
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student1.vue

    <template>
      <div>    
        <h1>案例1.2:子组件向父组件传值(或者叫:子组件调用父组件方法),修改父组件属性count,msg值</h1>
        <button @click="childTransmitParentProperty()">子组件内调用父组件方法修改count,msg值</button>
      </div>
    </template>
    
    <script>
    
    export default {
      name: "Student1",
      data() {
        return {}
      },
      methods: {
        //案例1.2:父组件传递子组件方法(或者叫:子组件调用父组件方法修改父组件属性值)
        childTransmitParentProperty() {
          this.$emit("receivePCount", 70, "你是个der")
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    页面展示效果
    在这里插入图片描述
    点击前:
    在这里插入图片描述
    点击后:
    在这里插入图片描述

    案例1.3:父组件调用子组件方法,修改子组件属性值,采用$refs方式实现

    采用$refs方式实现

    注意点1:
    语法:父组件使用子组件标签时配置ref=“student1”,父组件方法中this.$refs.student1= 子组件的vue实例
    大白话讲:在父组件中想调用子组件的属性或者方法,需要获取子组件的vue实例,通过ref标签即可获取

    父组件School.vue

    <template>
      <div>
        <student1 ref="student1"></student1>
        <h2>案例1.3:父组件调用子组件方法,修改子组件属性值</h2>
        <button @click="pRemSize()">父组件调用子组件方法减少size值</button><hr>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    
    export default {
      name: "School",
      components: {Student1},
      data() {
        return {}
      },
      methods: {    
        //案例1.3:父组件直接使用子组件方法,修改子组件属性值
        pRemSize() {
          this.$refs.student1.remSize()
        }   
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student1.vue

    <template>
      <div>
        <p>这是学生1子组件</p>
        <p>学生1子组件的属性size:{{size}}</p> 
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      data() {
        return {
          size:110
        }
      },
      methods: {
        remSize() {
          this.size--;
        }   
      }
    }
    </script>
    
    <style scoped></style>
    

    页面展示效果
    在这里插入图片描述
    点击前:
    在这里插入图片描述
    点击后:
    在这里插入图片描述

    案例1.4:父组件直接修改子组件属性值,采用$refs方式实现

    采用$refs方式实现

    注意点1:
    语法:父组件使用子组件标签时配置ref=“student1”,父组件方法中this.$refs.student1= 子组件的vue实例
    大白话讲:在父组件中想调用子组件的属性或者方法,需要获取子组件的vue实例,通过ref标签即可获取

    父组件School.vue

    <template>
      <div>   
        <student1 ref="student1"></student1>
        <h2>案例1.4:父组件直接修改子组件属性值</h2>
        <button @click="parentModifyChildProperty()">父组件直接修改子组件属性size值</button><hr>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    
    export default {
      name: "School",
      components: {Student1},
      data() {
        return {}
      },
      methods: {   
        //案例1.4:父组件直接修改子组件属性值
        parentModifyChildProperty() {
          this.$refs.student1.size = -1;
        }   
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student1.vue

    <template>
      <div>
        <p>这是学生1子组件</p>
        <p>学生1子组件的属性size:{{size}}</p>    
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      data() {
        return {
          size:110
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    页面展示效果
    在这里插入图片描述
    点击前:
    在这里插入图片描述
    点击后:
    在这里插入图片描述

    案例2.1:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性,采用v-bind方式实现

    采用v-bind方式实现

    注意点1:默认情况两个子组件之间是无法直接通信的,所以需要构建父组件,通过把父组件当做一个传递桥梁进而实现平行组件间之间通信。(即“子组件student1”把数据传递给“父组件school”,然后“父组件school”再把数据传递给“子组件student2”使用)

    父组件School.vue

    <template>
      <div>   
        <student1 ref="student1" @receivePCount="add(arguments)" ></student1>
        <student2 ref="student2" :stu1TransformCount="count" :stu1TransformMsg="msg"></student2>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    import Student2 from './Student2'
    
    export default {
      name: "School",
      components: {Student2, Student1},
      data() {
        return {
          count: 5,
          msg:""
        }
      },
      methods: {
        //子组件向父组件通信
        add(args){
          this.count += args[0];
          this.msg += args[1];
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student1.vue

    <template>
      <div>
        <h2>案例2.1:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性</h2>
        <button @click="childTransmitParentProperty()">平行组件Student1发送数据给Student2</button><hr>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      data() {
        return {}
      },
      methods: {    
        //案例1.2:父组件传递子组件方法(或者叫:子组件调用父组件方法修改父组件属性值)
        childTransmitParentProperty() {
          this.$emit("receivePCount", 70, "你是个der")
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student2.vue

    <template>
      <div>
        <p>这是子组件学生2</p>
        <p>平行组件通信:接收的学生组件1发过来的数据:{{stu1TransformCount}}--{{stu1TransformMsg}}</p>    
      </div>
    </template>
    
    <script>
    export default {
      name: "Student2",
      data() {
        return {}
      },
      props:{
        stu1TransformCount:null,
        stu1TransformMsg:null
      }
    }
    </script>
    
    <style scoped></style>
    

    页面展示效果
    在这里插入图片描述
    点击前:
    在这里插入图片描述
    点击后:
    在这里插入图片描述

    案例2.2:平行组件之间通信:Student2组件内调用Student1组件方法修改Student1组件属性,采用$refs和v-on(或者叫@自定义事件)方式实现

    采用$refs和v-on(或者叫@自定义事件)方式实现

    注意点1:默认情况两个子组件之间是无法直接通信的,所以需要构建父组件,通过把父组件当做一个传递桥梁进而实现平行组件间之间通信。(即“子组件student1”把数据传递给“父组件school”,然后“父组件school”再把数据传递给“子组件student2”使用)

    父组件School.vue

    <template>
      <div>
        <student1 ref="student1"></student1>   
        <student2 ref="student2"  @receiveStu1Method="invokeStu1Method"></student2>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    import Student2 from './Student2'
    
    export default {
      name: "School",
      components: {Student2, Student1},
      data() {
        return {}
      },
      methods: {   
        invokeStu1Method() {
          this.$refs.student1.remSize()
        }
      },
    }
    </script>
    
    <style scoped></style>
    

    子组件Student1.vue

    <template>
      <div>
        <p>这是学生1子组件</p>
        <p>学生1子组件的属性size:{{size}}</p>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      data() {
        return {
          size:110
        }
      },
      methods: {
        remSize() {
          this.size--;
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    子组件Student2.vue

    <template>
      <div>
        <h2>案例2.2:平行组件之间通信:Student2组件内调用Student1组件方法修改Student1组件属性</h2>
        <button @click="invokeStu1Method()">平行组件Student2调用Student1方法,修改size值</button><hr>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student2",
      data() {
        return {}
      },
      props:{
        receiveStu1Method:function () {}
      },
      methods:{
        invokeStu1Method() {
          this.$emit("receiveStu1Method")
        }
      }
    }
    </script>
    
    <style scoped></style>
    

    页面展示效果
    在这里插入图片描述
    点击前:
    在这里插入图片描述
    点击后:
    在这里插入图片描述

    案例2.3:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性,采用“全局事件总线”实现

    采用“全局事件总线”实现

    说明:使用“全局事件总线”只需要2个平行组件通信就行,跟v-bind方式区别在于,v-bind方式需要使用父组件当一个传递者,而“全局事件总线”不需要传递者这么个角色

    注意点1:

    问题:“全局事件总线”需要哪些特点?

    答案:
    1)被所有组件(vc、vm)能够看得见
    2)能够调用$on、$emit、$off

    注意点2:

    问题:Vue原型对象上面的所有属性和方法是给谁用的?

    答案:是给所有的vm和vc使用的

    注意点3:

    问题:为什么定义“全局事件总线”要放在main.js文件中?

    答案:因为哪里引入Vue,哪里才会去定义“全局事件总线”

    注意点4:

    问题:为什么定义“全局事件总线”要放在beforeCreate的钩子函数中?

    答案:原因1,beforeCreate钩子函数里this指代new出来的vm,原因2,在beforeCreate钩子函数里模板还没解析,数据监测和数据代理也还没完成呢。也就是说借助这个beforeCreate钩子函数你把想做的事儿做好了,原型上该放的放好了,随后模板开始解析,等组件执行的时候你该放的都放好了,后续才做都不会产生影响。

    注意点5:

    问题:如何避免在使用“全局事件总线”时自定义函数名重名使用问题?比如组件1使用自定义函数名叫demo,那组件2不全文搜索也使用了自定义函数名也叫demo,这就混了

    答案:真实项目中src目录下创建一个config文件夹,里面创建个constants常量文件,里面用来定义要使用的自定义函数名,方便别人查看并避免重名问题。

    注意点6:

    问题:为什么要在组件销毁之前,把“全局事件总线”中定义的自定义事件函数解绑?那“知识点3.13自定义事件”中咋没说解绑的事儿呢?

    答案:“知识点3.13自定义事件”中组件销毁了== vc销毁了,vc销毁了自定义事件也就销毁了,而“全局事件总线”中定义的自定义函数是一直存在的,哪怕使用组件销毁了,但是Vue实力定义的“全局事件总线”中还是会存在自定义事件,所以需要在组件销毁之前进行解绑。

    注意点7:销毁“全局事件总线”中定义的自定义事件请放在beforeDestroy()钩子中

    注意点8:子组件中使用“全局事件总线”时this.$bus.$on()中回调配置要使用箭头函数,不要使用普通函数,箭头函数中this才指代vc,而普通函数中this指代vue实例,因为最终要在school组件上接收平行组件发过来的消息,所以要使用vc,而不是要使用vue实例,因为vue实例不是我们最终要的。

    项目结构

    在这里插入图片描述

    完整代码

    main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	beforeCreate() {
    		Vue.prototype.$bus = this
    	},
    })
    

    App.vue

    <template>
    	<div id="app">
    		<div>
    			<School></School>
    		</div>
    	</div>
    </template>
    
    <script>
      import School from "./components/School";
    
    	export default {
    		name:'App',
    		components:{School},
    		data() {
    			return {
    
    			}
    		},
    		methods: {
    
        }
    	}
    </script>
    

    School.vue

    <template>
      <div>
        <student1></student1>
        <student2></student2>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    import Student2 from './Student2'
    
    export default {
      name: "School",
      components: {Student2, Student1},
      data() {
        return {}
      },
      methods: {
    
      },
    }
    </script>
    
    <style scoped></style>
    

    Student1.vue

    <template>
      <div class="student">
        <h2>学生1姓名:{{name}}</h2>
        <h2>学生1性别:{{sex}}</h2>
        <button @click="sendToStudent2">把学生名给School组件</button>
      </div>
    </template>
    
    <script>
    export default {
      name:'Student',
      data() {
        return {
          name:'张三',
          sex:'男',
        }
      },
      methods: {
        sendToStudent2(){
          this.$bus.$emit('hello',this.name, this.sex)
        }
      },
    }
    </script>
    
    <style lang="less" scoped>
    .student{
      background-color: pink;
      padding: 5px;
      margin-top: 30px;
    }
    </style>
    

    Student2.vue

    <template>
      <div class="school">
        <h2>学生2名称:{{name}}</h2>
        <h2>学生2地址:{{address}}</h2>
      </div>
    </template>
    
    <script>
    export default {
      name:'School',
      data() {
        return {
          name:'李四',
          address:'女',
        }
      },
      mounted() {
        this.$bus.$on('hello',(arguements)=>{
          console.log('我是Student2组件,收到了数据:',arguements[0], arguements[1])
        })
      },
      beforeDestroy() {
        this.$bus.$off('hello')
      },
    }
    </script>
    
    <style scoped>
    .school{
      background-color: skyblue;
      padding: 5px;
    }
    </style>
    

    结果展示

    在这里插入图片描述

    案例2.4:平行组件之间通信:Student2组件内打印展示Student1组件传递过来的属性,采用“消息订阅与发布”实现

    使用步骤:

    在这里插入图片描述

    采用“消息订阅与发布”实现

    注意点0:这块知识点如果不太懂,可以查看我自己总结的博客:vue2知识点:消息订阅与发布

    注意点1:由于“消息订阅与发布”可依赖的第三方太多了,这里使用pubsub-js

    注意点2:使用语法

    消息订阅语法

    import pubsub from 'pubsub-js'
    
    mounted() {
    this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
    	// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
    })
    },
    beforeDestroy() {
    	// this.$bus.$off('hello')
    	pubsub.unsubscribe(this.pubId)
    }
    

    消息发布语法

    import pubsub from 'pubsub-js'
    
    pubsub.publish('hello',666)
    

    注意点3:取消订阅方式和“全局事件总线”不同,取消订阅指定订阅返回的id,且每次返回的id都不同,而“全局事件总线”指定的是“自定义事件名称”

    注意点4:订阅回调配置一定要使用箭头函数或者外部定义方法,在订阅中引用也行,千万不要使用普通函数,因为普通函数中this不指代vc,而是undefine,这一点跟“全局事件总线”中的注意点8很像,但还是略有不同

    注意点5:消息订阅会接收到2个参数,第1个参数为消息名称,第2个参数才是传递过来的值,如写法1,但是实际msgName参数1他跟用不到它,所以可使用下划线“_”占个位,如写法2

    写法1:

    this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
    	// console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
    })
    

    写法2:

    this.pubId = pubsub.subscribe('hello',(_,data)=>{
    	// console.log('有人发布了hello消息,hello消息的回调执行了',_,data)
    })
    

    注意点6:箭头函数中的名称(msgName,data)=>{}可以随便写,但是避免使用使用关键字名字

    注意点7:如果想传递多个参数,需使用{}
    发送方

    sendToStudent2(){
      pubsub.publish('hello',{name:this.name, sex:this.sex})
    }
    

    接收方

    mounted() {
        this.pubId = pubsub.subscribe('hello',(msgName, object)=>{
          console.log('有student1平行组件发布了hello消息,hello消息的回调执行了:',object.name, object.sex)
        })
      }
    

    项目结构

    在这里插入图片描述

    完整代码

    main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App)
    })
    

    App.vue

    <template>
    	<div id="app">
    		<div>
    			<School></School>
    		</div>
    	</div>
    </template>
    
    <script>
      import School from "./components/School";
    
    	export default {
    		name:'App',
    		components:{School},
    		data() {
    			return {
    
    			}
    		},
    		methods: {
    
        }
    	}
    </script>
    

    School.vue

    <template>
      <div>
        <student1></student1>
        <student2></student2>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    import Student2 from './Student2'
    
    export default {
      name: "School",
      components: {Student2, Student1},
      data() {
        return {}
      },
      methods: {
    
      },
    }
    </script>
    
    <style scoped></style>
    

    Student1.vue

    <template>
      <div class="student">
        <h2>学生1姓名:{{name}}</h2>
        <h2>学生1性别:{{sex}}</h2>
        <button @click="sendToStudent2">把学生名给School组件</button>
      </div>
    </template>
    
    <script>
    import pubsub from 'pubsub-js';
    
    export default {
    
      name:'Student',
      data() {
        return {
          name:'张三',
          sex:'男',
        }
      },
      methods: {
        sendToStudent2(){
          pubsub.publish('hello',{name:this.name, sex:this.sex})
        }
      },
    }
    </script>
    
    <style lang="less" scoped>
    .student{
      background-color: pink;
      padding: 5px;
      margin-top: 30px;
    }
    </style>
    

    Student2.vue

    <template>
      <div class="school">
        <h2>学生2名称:{{name}}</h2>
        <h2>学生2地址:{{address}}</h2>
      </div>
    </template>
    
    <script>
    import pubsub from 'pubsub-js';
    
    export default {
      name:'School',
      data() {
        return {
          name:'李四',
          address:'女',
        }
      },
      mounted() {
        this.pubId = pubsub.subscribe('hello',(msgName, object)=>{
          console.log('有student1平行组件发布了hello消息,hello消息的回调执行了:',object.name, object.sex)
        })
      },
      beforeDestroy() {
        pubsub.unsubscribe(this.pubId)
      },
    }
    </script>
    
    <style scoped>
    .school{
      background-color: skyblue;
      padding: 5px;
    }
    </style>
    

    结果展示

    在这里插入图片描述

    案例2.5:平行组件之间通信:Student1组件和Student2组件共同读取vuex共享数据,并实现修改vuex数据,采用“vuex方式”实现

    使用步骤:

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    注意点0:如果vuex相关知识点不太了解,可以查看我自己总结的博客进行了解学习。
    vue2知识点:理解vuex、安装vuex、搭建vuex环境

    项目目录

    在这里插入图片描述

    main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    //引入store
    import store from './store'
    
    //创建vm
    new Vue({
        el:'#app',
        render: h => h(App),
        store
    })
    

    App.vue

    <template>
      <div id="app">
        <School></School>
      </div>
    </template>
    
    <script>
    import School from "./components/School";
    
    export default {
      name:'App',
      components: {School}
    }
    </script>
    

    index.js

    //该文件用于创建Vuex中最为核心的store
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions——用于响应组件中的动作
    const actions = {
        //响应式组件中加的动作
        jia(context, value) {
            context.commit('JIA', value);
        }
    }
    //准备mutations——用于操作数据(state)
    const mutations = {
        //执行加
        JIA(state, value) {
            state.sum += value;
        }
    }
    //准备state——用于存储数据
    const state = {
        sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
        actions,
        mutations,
        state,
    })
    

    School.vue

    <template>
      <div>
        <Student1></Student1>
        <hr>
        <Student2></Student2>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    import Student2 from './Student2'
    
    export default {
      name: "School",
      components:{Student1, Student2}
    }
    </script>
    

    Student1.vue

    <template>
      <div>
        <h1>我是子组件Student1</h1>
        <h2>读取vuex中的共享数据sum值:{{$store.state.sum}}}</h2>
        <button @click="add">点击sum+1</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      methods:{
        add() {
          this.$store.dispatch("jia", 1);
        }
      }
    }
    </script>
    

    Student2.vue

    <template>
      <div>
        <h1>我是子组件Student2</h1>
        <h2>读取vuex中的共享数据sum值:{{$store.state.sum}}}</h2>
        <button @click="add">点击sum+2</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student2",
      methods:{
        add() {
          this.$store.dispatch("jia", 2);
        }
      }
    }
    </script>
    

    结果展示

    在这里插入图片描述

    案例3.1:嵌套父子组建通信,实现父组件一次性给所有子孙组件传值,采用“provide/inject”方式

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

    注意点1:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。仔细看最终案例结果展示username属性传递的是对象,所以是响应式的;而otherNme传递的是普通字符串,是非响应的,因为子组件值未更新。

    使用场景:祖先组建一次性给所有子孙组件传递属性。

    优点是:节省代码,不需要每个子组件使用v-bind之类的挨个绑定

    项目目录,其中School是父组件,Student1是子组建

    在这里插入图片描述

    项目代码

    main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //关闭Vue的生产提示
    Vue.config.productionTip = false
    
    //创建vm
    new Vue({
        el:'#app',
        render: h => h(App),
    })
    

    App.vue

    <template>
      <div id="app">
        <School></School>
      </div>
    </template>
    
    <script>
    import School from "./components/School";
    
    export default {
      name:'App',
      components: {School}
    }
    </script>
    

    School.vue

    <template>
      <div>
        <h1>我是嵌套父组件School,我的name属性值为:{{obj.name}}</h1>
        <button @click="modifyName">修改School组件的name值</button>
        <h1>我是嵌套父组件School,我的otherName属性值为:{{otherName}}</h1>
        <button @click="modifyOtherName">修改School组件的otherName值</button>
        <hr>
        <Student1 ref="MyStudent"></Student1>
      </div>
    </template>
    
    <script>
    import Student1 from './Student1'
    
    export default {
      name: "School",
      components:{Student1},
      data() {
        return {
          name: "我是嵌套父组件School",
          otherName: "小白",
          obj: {name: "cat"}
        }
      },
      methods:{
        modifyName() {
          this.obj.name = "cow";
        },
        modifyOtherName() {
          this.otherName = "小黑";
        }
      },
      provide() {
        return {
          otherName: this.otherName,  //此处provice一个普通字符串,测试实现数据的响应式
          username: this.obj          //此处provice一个对象,测试实现数据的响应式
        }
      }
    }
    </script>
    

    Student1.vue

    <template>
      <div>
        <h1>我是子组件Student1</h1>
        <h2>获取嵌套父组件School传过来的对象username值:{{username}}</h2>
        <h2>获取嵌套父组件School传过来的普通字符串otherName值:{{otherName}}</h2>
      </div>
    </template>
    
    <script>
    export default {
      name: "Student1",
      inject: ["username", "otherName"],
      data() {
        return {
          name: "我是子组件Student1"
        }
      }
    }
    </script>
    

    结果展示

    在这里插入图片描述

    案例4.1:获取父 / 子组件实例,采用“$parent / $children与 ref”方式

    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    • $parent / $children:访问父 / 子实例

    注意点1:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。

    改动地方,School.vue组件中使用子组件Student1 标签中,定义ref属性,同时打印 p a r e n t 和 parent和 parentchildren

    <Student1 ref="MyStudent"></Student1>
    
    mounted() {
        console.log("输出子组建Student1的父实例:" ,this.$refs.MyStudent.$parent)
        console.log("输出父组件School的子实例:" ,this.$children)
      }
    

    结果展示:
    输出子组建Student1的父实例,调用$parent实例结果
    在这里插入图片描述

    输出父组件School的子实例,调用$children实例结果
    在这里插入图片描述

    三、html项目页面实现平行组件通信案例

    上面的代码是针对vue-cli创建的vue项目实现组件通信的方案,然而在刚学习vue时都是创建html页面引入vue.js去练习vue技术的,接下来介绍的方案就是针对html页面中组件通信的。

    语法:$emit和$on,同一页面中通过创建Event实例去传递使用数据

    问题要求:有<v-a>、<v-b>、<v-c>3个子组件,他们三个处于平行关系,用来接收或者组件发过来的消息

    子组件中调用Event.$emit(‘asend’, this.ipt)调用
    子组件中接收Event.$on(‘bsend’, function (msg) { _this.strb = msg;})

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Vue练习</title>
        <!--引入Vue-->
        <script type="text/javascript" src="../../static/vue/vue.js" ></script>
    </head>
    <body>
        <!--
        平行组件间调用属性和方法:
            问题要求:有<v-a>、<v-b>、<v-c>3个子组件,他们三个处于平行关系,用<v-c>来接收<v-a>或者<v-b>组件发过来的消息
            子组件中调用Event.$emit('asend', this.ipt)调用
            子组件中接收Event.$on('bsend', function (msg) {
                                    _this.strb = msg;
                                })
        -->
        <div id="app">
            <v-a></v-a><hr>
            <v-b></v-b><hr>
            <v-c></v-c>
        </div>
    
        <template id="a">
            <div>
                <p>这是a组件</p>
                <button @click="a()">发送a组件</button>
            </div>
        </template>
        <template id="b">
            <div>
                <p>这是b组件</p>
                <button @click="b()">发送b组件</button>
            </div>
        </template>
        <template id="c">
            <div>
                <p>这是c组件</p>
                <p>接收的a数据:{{stra}}</p>
                <p>接收的b数据:{{strb}}</p>
            </div>
        </template>
    
    
        <script type="text/javascript">
            var Event = new Vue();
    
            new Vue({
                el: '#app',
                data: {},
                methods: {},
                components: {
                    "v-a": {
                        template: "#a",
                        data() {
                            return {
                                ipt:"我是a组件发过来数据"
                            }
                        },
                        methods: {
                            a() {
                                Event.$emit('asend', this.ipt)
                            }
                        }
                    },
                    "v-b": {
                        template: '#b',
                        data() {
                            return{
                                ipt:"我是b组件发过来数据"
                            }
                        },
                        methods: {
                            b() {
                                Event.$emit('bsend', this.ipt)
                            }
                        }
                    },
                    "v-c": {
                        template: '#c',
                        data() {
                            return {
                                stra:"",
                                strb:""
                            }
                        },
                        mounted() {
                            var _this = this;
                            //接收a数据
                            Event.$on('asend', function (msg) {
                                _this.stra = msg;
                            })
                            //接收b数据
                            Event.$on('bsend', function (msg) {
                                _this.strb = msg;
                            })
                        }
                    }
                }
            })
        </script>
    </body>
    </html> 
    

    点击按钮前:
    在这里插入图片描述
    点击按钮后:
    在这里插入图片描述

    四、使用方式总结

    常见使用场景可以分为三类:

    父子通信:

    • 父向子传递数据是通过 props,子向父是通过$emit和$on或者v-on
    • 多级父子组件嵌套想获取父/子的vc实例,请使用$parent / $children
    • ref 也可以访问组件实例
    • 父组件一次性给所有子孙组件传递属性,请使用provide / inject
    • $attrs/$listeners不常用,可忽略或者了解即可

    平行组件通信:

    • 全局事件总线,请使用Bus(推荐使用),优点vue开发完全友好支持,适用于很小项目或者练习
    • 消息订阅发布,属于使用第三方(不太推荐使用)
    • vuex,共享数据(推荐使用),同样是vue开发完全友好支持,vuex和全局事件总线区别是:大项目请使用vuex,因为共享数据多,小范围共享数据请使用全局事件总线Bus

    跨级通信:

    • Bus
    • Vuex
    • provide / inject API
    • $attrs/$listeners

    本人其他相关文章链接

    1.《基础篇第1章:vue2简介》包含Vue2知识点、个人总结的使用注意点及碰到的问题总结

    2.《基础篇第2章:vue2基础》包含Vue2知识点、个人总结的使用注意点及碰到的问题总结

    3.《进阶篇第3章:vue进阶-组件》包含组件、自定义事件、插槽、路由等等扩展知识点

    4.《基础篇第4章》:使用vue脚手架创建项目

    5.vue2知识点:数据代理

    6.vue2知识点:事件处理

    7.vue2知识点:列表渲染(包含:v-for、key、取值范围、列表过滤、列表排序、vue监视对象或数组的数据改变原理、总结vue数据监测)

    8.vue2知识点:计算属性与监听属性

    9.vue2知识点:生命周期(包含:生命周期介绍、生命周期钩子、整体流程图详解)

    10.vue2知识点:非单文件组件和单文件组件

    11.vue2知识点:组件is属性

    12.vue2知识点:组件模板定义

    13.vue2知识点:组件的props属性、非props属性、props属性校验

    14.vue2知识点:组件自定义事件

    15.vue2知识点:组件插槽分发

    16.vue2知识点:动态组件

    17.vue2知识点:混入

    18.vue2知识点:浏览器本地缓存

    19.vue2知识点:全局事件总线(GlobalEventBus)

    20.vue2知识点:消息订阅与发布

    21.vue2知识点:nextTick语法

    22.vue2知识点:Vue封装的过度与动画

    23.vue2知识点:路由

    24.vue2知识点:vm调用待$命令介绍

    25.vue组件通信案例练习(包含:父子组件通信及平行组件通信)

    26.vue表单案例练习:vue表单创建一行数据及删除数据的实现与理解

    27.vue2基础组件通信案例练习:待办事项Todo-list案例练习

    28.vue2基础组件通信案例练习:把案例Todo-list改写成本地缓存

    29.vue2基础组件通信案例练习:把案例Todo-list改成使用自定义事件

    30.vue2基础组件通信案例练习:把案例Todo-list改成使用全局事件总线

    31.vue2基础组件通信案例练习:把案例Todo-list改成使用消息订阅与发布

    32.vue2基础组件通信案例练习:把案例Todo-list新增编辑按钮

    33.vue2基础组件通信案例练习:把案例Todo-list改成使用动画与过度

    34.学习vue2遇到过的问题及个人总结

    展开全文
  • 装依赖 npm install 启动 npm run serve 这是一个vue组件的几种通信方式的源码
  • 前言 组件是 vue.js最强大的功能之一...本文总结了vue组件通信的几种方式,如props、 $emit / $on 、vuex、 $parent / $children 、 $attrs / $listeners 和provide/inject,以通俗易懂的实例讲述这其中的差别及使用
  • vue 组件通信的几种方式

    千次阅读 2022-02-10 15:05:34
    vue组件通信的N种方式。

    前言

    在vue中,​ 组件的关系不外乎以下三种:
    在这里插入图片描述
    组件是需要通信的,在开发中,常用到的通信方式有:vuex、eventBus、以及props与emit、$parent与$children,除此之外,还有provide与inject、$attrs与$listeners等。

    一、vuex

    这个相信大家用的很多了,简单回顾一下:

    • State:放状态的地方
    • Mutation:唯一修改状态的地方,不支持异步
    • Action:通过调用Mutation中的方法来达到修改状态的目的,支持异步
    • Getter:可以理解为计算属性
    • Module:模块,每个模块拥有自己的 state、mutation、action、getter
      简单的使用这里不赘述,提一下module里面的命名空间。

    如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名

    在这里插入图片描述
    这样,在使用的时候我们就可以这样用了:
    在这里插入图片描述

    二、eventBus

    这个称为‘事件总线’,简单看下是怎么使用的:

    • 初始化
      首先是初始化一个eventBus,可以绑定到vue原型上,也可以绑定到window对象上,还可以抽出来当做一个模块,在需要的时候再引入。这里直接绑定到vue原型上:
      在这里插入图片描述

    • 创建事件和删除事件
      在需要的组件上创建和删除事件:
      在这里插入图片描述

    • 触发事件
      最后就是在需要的地方触发事件了
      在这里插入图片描述

    三、props/emit

    这个不用多说了,父子通信用的最多的应该就是这个了。当然,如果以子组件为跳板,也可以做到祖孙之间通信,不过比较麻烦。不建议这样操作。

    四、$parent/$children

    $parent直接访问的就是父实例,而$children则返回的是实例数组。所以我一般都是$parent搭配$refs使用。

    五、$attrs/$listeners

    这两个可能会用的比较少,来看下官网的介绍:
    在这里插入图片描述
    怎么理解呢,简单来讲就是,$attrs接收除了prop、style、class之外的所有绑定属性,$listeners则接收除了被.native修饰的所有绑定事件。具体来看下例子:

    <template>
      <div>
        <p>父组件</p>
        <input type="text" v-model="formData.inputValue" />
        <p>子组件</p>
        <Son
          :inputValue="formData.inputValue"
          :otherValue="otherValue"
          @success="success"
          @input.native="handleInput"
          v-bind="$attrs"
          v-on="$listeners"
        ></Son>
      </div>
    </template>
    <script>
    import Son from "./son.vue";
    export default {
      components: { Son },
      provide() {
        return {
          father: this.formData,
        };
      },
      data() {
        return {
          formData: {
            inputValue: "123",
          },
          otherValue: 999,
        };
      },
      methods: {
        success(data) {
          console.log(data);
        },
        handleInput() {},
      },
    };
    </script>
    
    
    <template>
      <div>
        <input type="text" v-model="inputValue" @change="handleChange" />
      </div>
    </template>
    <script>
    export default {
      props: {
        inputValue: String,
      },
      created() {
        console.log(this.$attrs, "son---$attrs");
        console.log(this.$listeners, "son---$listeners");
      },
      methods: {
        handleChange() {
          this.father.inputValue = this.inputValue;
        },
      },
    };
    </script>
    

    按照之前的理解,$attrs应该只能接收到otherValue,$listeners则只能接收到success事件,看下打印结果:
    在这里插入图片描述
    结果确实也是这样的。除此之外,还可传递给孙组件:

    <template>
      <div>
        <input type="text" v-model="inputValue" @change="handleChange" />
        <GrandSon v-bind="$attrs" v-on="$listeners"></GrandSon>
      </div>
    </template>
    <script>
    import GrandSon from "./grandSon.vue";
    export default {
      components: { GrandSon },
      props: {
        inputValue: String,
      },
      created() {
        console.log(this.$attrs, "son---$attrs");
        console.log(this.$listeners, "son---$listeners");
      },
      methods: {
        handleChange() {
          this.father.inputValue = this.inputValue;
        },
      },
    };
    </script>
    
    
    <template>
      <div>
        <input type="text" v-model="inputValue" @change="handleChange" />
      </div>
    </template>
    <script>
    export default {
      props: {
        inputValue: String,
      },
      created() {
        console.log(this.$attrs, "grandSon---$attrs");
        console.log(this.$listeners, "grandSon---$listeners");
      },
      methods: {
        handleChange() {
          this.father.inputValue = this.inputValue;
        },
      },
    };
    </script>
    

    在这里插入图片描述
    通过这种方式,祖孙之间也实现了通信。

    六、provide/inject

    provide/inject可以在一个祖先组件中向它的所有后辈组件注入一个依赖,只要上下游关系成立就能生效。简单的理解就是provide是注入数据,inject是获取数据。所以provide是用于父组件,inject是用于子孙组件。provide应该是一个对象或者返回一个对象的函数,inject应该是一个字符串数组或者一个对象。官网提到这么一句话:

    提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

    这句话怎么理解呢?字面理解就是你要想在上下游传递的那个数据是可响应的,那么就应该以对象的形式传递,先试一下以基本数据类型的形式传递,看下例子:
    父组件:

    <template>
      <div>
        <p>父组件</p>
        <input type="text" v-model="inputValue" />
        <p>子组件</p>
        <Son></Son>
        <p>孙组件</p>
        <GrandSon></GrandSon>
      </div>
    </template>
    <script>
    import Son from "./son.vue";
    import GrandSon from "./grandSon.vue";
    export default {
      components: { Son, GrandSon },
      provide() {
        return {
          father: this.inputValue,
        };
      },
      data() {
        return {
          inputValue: "123",
        };
      },
    };
    </script>
    
    

    子组件:

    <template>
      <div>
        <input type="text" v-model="inputValue" @change="handleChange" />
      </div>
    </template>
    <script>
    export default {
      inject: ["father"],
      data() {
        return {
          inputValue: "",
        };
      },
      watch: {
        father(val) {
          console.log(val, "val");
          this.inputValue = val;
        },
      },
      created() {
        console.log(this, "this");
      },
      methods: {
        handleChange() {
          this.father.inputValue = this.inputValue;
        },
      },
    };
    </script>
    
    

    在子组件打印this:
    在这里插入图片描述
    可以看到,父组件的inputValue值是被注入到子组件当中的。但却监听不到这个father。
    请添加图片描述
    然后,我们改成以对象的形式进行注入:

    <template>
      <div>
        <p>父组件</p>
        <input type="text" v-model="formData.inputValue" />
        <p>子组件</p>
        <Son></Son>
        <p>孙组件</p>
        <GrandSon></GrandSon>
      </div>
    </template>
    <script>
    import Son from "./son.vue";
    import GrandSon from "./grandSon.vue";
    export default {
      components: { Son, GrandSon },
      provide() {
        return {
          father: this.formData,
        };
      },
      data() {
        return {
          formData: {
            inputValue: "123",
          },
        };
      },
    };
    </script>
    
    
    <template>
      <div>
        <input type="text" v-model="inputValue" @change="handleChange" />
      </div>
    </template>
    <script>
    export default {
      inject: ["father"],
      data() {
        return {
          inputValue: "",
        };
      },
      watch: {
        'father.inputValue'(val){
          console.log(val, "val");
          this.inputValue = val;
        },
      },
      created() {
        console.log(this, "this");
      },
      methods: {
        handleChange() {
          this.father.inputValue = this.inputValue;
        },
      },
    };
    </script>
    

    这个时候我们看下打印的this以及效果:
    在这里插入图片描述
    请添加图片描述
    这样就可以实现数据的响应了。这里有一个点需要注意,如果在父组件中将整个父组件的this注入到后代组件中,在后代组件中是不能通过深度监听来监听这个注入的对象的,会报堆栈溢出的错误。所以这里我用的是this.formData的形式注入。这样在子孙组件中可以通过'father.inputValue'这样的形式监听,也可以通过这样的形式:

    father: {
          handler(val) {
            console.log(val);
          },
          deep: true,
        },
    

    至于为什么会导致这个问题,我们先看下深度监听的实现方式:
    在这里插入图片描述
    这段注释什么意思呢,简单理解就是vue是通过递归遍历对象里面的每一个属性,将是对象的属性收集起来进行监听。众所周知,递归是很容易引起堆栈溢出的,而看下this对象就不难理解为什么会导致堆栈溢出了(太多了,而且是层层嵌套下去的)。
    以上就是Vue组件通信的几种方式,如果还要在扯一扯,浏览器的缓存也可以作为一种手段。。。

    展开全文
  • Vue组件通信的六种方式

    千次阅读 多人点赞 2022-02-02 11:32:59
    前言 组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法...本文总结了vue组件通信的几种方式,如props、$emit/$on、vuex、$parent / $children、$attrs/$listen

    前言

    组件是 vue.js最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。一般来说,组件可以有以下几种关系:

    如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。

    针对不同的使用场景,如何选择行之有效的通信方式?这是我们所要探讨的主题。本文总结了vue组件间通信的几种方式,如props、$emit/$on、vuex、$parent / $children$attrs/$listeners和provide/inject,以通俗易懂的实例讲述这其中的差别及使用场景,希望对小伙伴有些许帮助。

    本文的代码请猛戳github博客,纸上得来终觉浅,大家动手多敲敲代码!

    方法一、props/$emit

    父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。

    1.父组件向子组件传值

    接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据 users:["Henry","Bucky","Emily"]

    //App.vue父组件
    <template>
      <div id="app">
        <users v-bind:users="users"></users>//前者自定义名称便于子组件调用,后者要传递数据名
      </div>
    </template>
    <script>
    import Users from "./components/Users"
    export default {
      name: 'App',
      data(){
        return{
          users:["Henry","Bucky","Emily"]
        }
      },
      components:{
        "users":Users
      }
    }
    复制代码
    //users子组件
    <template>
      <div class="hello">
        <ul>
          <li v-for="user in users">{{user}}</li>//遍历传递过来的值,然后呈现到页面
        </ul>
      </div>
    </template>
    <script>
    export default {
      name: 'HelloWorld',
      props:{
        users:{           //这个就是父组件中子标签自定义名字
          type:Array,
          required:true
        }
      }
    }
    </script>
    复制代码

    总结:父组件通过props向下传递数据给子组件。注:组件中的数据共有三种形式:data、props、computed

    2.子组件向父组件传值(通过事件形式)

    接下来我们通过一个例子,说明子组件如何向父组件传递值:当我们点击“Vue.js Demo”后,子组件向父组件传递值,文字由原来的“传递的是一个值”变成“子向父组件传值”,实现子组件向父组件值的传递。

    // 子组件
    <template>
      <header>
        <h1 @click="changeTitle">{{title}}</h1>//绑定一个点击事件
      </header>
    </template>
    <script>
    export default {
      name: 'app-header',
      data() {
        return {
          title:"Vue.js Demo"
        }
      },
      methods:{
        changeTitle() {
          this.$emit("titleChanged","子向父组件传值");//自定义事件  传递值“子向父组件传值”
        }
      }
    }
    </script>
    复制代码
    // 父组件
    <template>
      <div id="app">
        <app-header v-on:titleChanged="updateTitle" ></app-header>//与子组件titleChanged自定义事件保持一致
       // updateTitle($event)接受传递过来的文字
        <h2>{{title}}</h2>
      </div>
    </template>
    <script>
    import Header from "./components/Header"
    export default {
      name: 'App',
      data(){
        return{
          title:"传递的是一个值"
        }
      },
      methods:{
        updateTitle(e){   //声明这个函数
          this.title = e;
        }
      },
      components:{
       "app-header":Header,
      }
    }
    </script>
    复制代码

    总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。

    方法二、$emit/$on

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

    1.具体实现方式:

        var Event=new Vue();
        Event.$emit(事件名,数据);
        Event.$on(事件名,data => {});
    复制代码

    2.举个例子

    假设兄弟组件有三个,分别是A、B、C组件,C组件如何获取A或者B组件的数据

    <div id="itany">
    	<my-a></my-a>
    	<my-b></my-b>
    	<my-c></my-c>
    </div>
    <template id="a">
      <div>
        <h3>A组件:{{name}}</h3>
        <button @click="send">将数据发送给C组件</button>
      </div>
    </template>
    <template id="b">
      <div>
        <h3>B组件:{{age}}</h3>
        <button @click="send">将数组发送给C组件</button>
      </div>
    </template>
    <template id="c">
      <div>
        <h3>C组件:{{name}},{{age}}</h3>
      </div>
    </template>
    <script>
    var Event = new Vue();//定义一个空的Vue实例
    var A = {
    	template: '#a',
    	data() {
    	  return {
    	    name: 'tom'
    	  }
    	},
    	methods: {
    	  send() {
    	    Event.$emit('data-a', this.name);
    	  }
    	}
    }
    var B = {
    	template: '#b',
    	data() {
    	  return {
    	    age: 20
    	  }
    	},
    	methods: {
    	  send() {
    	    Event.$emit('data-b', this.age);
    	  }
    	}
    }
    var C = {
    	template: '#c',
    	data() {
    	  return {
    	    name: '',
    	    age: ""
    	  }
    	},
    	mounted() {//在模板编译完成后执行
    	 Event.$on('data-a',name => {
    	     this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
    	 })
    	 Event.$on('data-b',age => {
    	     this.age = age;
    	 })
    	}
    }
    var vm = new Vue({
    	el: '#itany',
    	components: {
    	  'my-a': A,
    	  'my-b': B,
    	  'my-c': C
    	}
    });	
    </script>
    复制代码

    $on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。

    方法三、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 名称', data1)来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。
    • commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
    • mutations:状态改变操作方法,由actions中的commit('mutation 名称')来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。
    • state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
    • getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。

    3.Vuex与localStorage

    vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

    let defaultCity = "上海"
    try {   // 用户关闭了本地存储功能,此时在外层加个try...catch
      if (!defaultCity){
        defaultCity = JSON.parse(window.localStorage.getItem('defaultCity'))
      }
    }catch(e){}
    export default new Vuex.Store({
      state: {
        city: defaultCity
      },
      mutations: {
        changeCity(state, city) {
          state.city = city
          try {
          window.localStorage.setItem('defaultCity', JSON.stringify(state.city));
          // 数据改变的时候把数据拷贝一份保存到localStorage里面
          } catch (e) {}
        }
      }
    })
    复制代码

    这里需要注意的是:由于vuex里,我们保存的状态,都是数组,而localStorage只支持字符串,所以需要用JSON转换:

    JSON.stringify(state.subscribeList);   // array -> string
    JSON.parse(window.localStorage.getItem("subscribeList"));    // string -> array 
    复制代码

    方法四、$attrs/$listeners

    1.简介

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

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

    • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

    接下来我们看个跨级通信的例子:

    // 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>
    复制代码

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

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

    方法五、provide/inject

    1.简介

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

    2.举个例子

    假设有两个组件: 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 是不会改变的,仍然是 浪里行舟。

    3.provide与inject 怎么实现数据响应式

    一般来说,有两种办法:

    • provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
    • 使用2.6最新API Vue.observable 优化响应式 provide(推荐)

    我们来看个例子:孙组件D、E和F获取A组件传递过来的color值,并能实现数据响应式变化,即A组件的color变化后,组件D、E、F会跟着变(核心代码如下:)

    // A 组件 
    <div>
          <h1>A 组件</h1>
          <button @click="() => changeColor()">改变color</button>
          <ChildrenB />
          <ChildrenC />
    </div>
    ......
      data() {
        return {
          color: "blue"
        };
      },
      // provide() {
      //   return {
      //     theme: {
      //       color: this.color //这种方式绑定的数据并不是可响应的
      //     } // 即A组件的color变化后,组件D、E、F不会跟着变
      //   };
      // },
      provide() {
        return {
          theme: this//方法一:提供祖先组件的实例
        };
      },
      methods: {
        changeColor(color) {
          if (color) {
            this.color = color;
          } else {
            this.color = this.color === "blue" ? "red" : "blue";
          }
        }
      }
      // 方法二:使用2.6最新API Vue.observable 优化响应式 provide
      // provide() {
      //   this.theme = Vue.observable({
      //     color: "blue"
      //   });
      //   return {
      //     theme: this.theme
      //   };
      // },
      // methods: {
      //   changeColor(color) {
      //     if (color) {
      //       this.theme.color = color;
      //     } else {
      //       this.theme.color = this.theme.color === "blue" ? "red" : "blue";
      //     }
      //   }
      // }
    复制代码
    // F 组件 
    <template functional>
      <div class="border2">
        <h3 :style="{ color: injections.theme.color }">F 组件</h3>
      </div>
    </template>
    <script>
    export default {
      inject: {
        theme: {
          //函数式组件取值不一样
          default: () => ({})
        }
      }
    };
    </script>
    复制代码

    虽说provide 和 inject 主要为高阶插件/组件库提供用例,但如果你能在业务中熟练运用,可以达到事半功倍的效果!

    方法六、$parent / $childrenref

    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    • $parent / $children:访问父 / 子实例

    需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。我们先来看个用 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>
    复制代码

    不过,这两种方法的弊端是,无法在跨级或兄弟间通信

    // parent.vue
    <component-a></component-a>
    <component-b></component-b>
    <component-b></component-b>
    复制代码

    我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案。

    总结

    常见使用场景可以分为三类:

    • 父子通信:

    父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners

    • 兄弟通信:

    Bus;Vuex

    • 跨级通信:

    Bus;Vuex;provide / inject API、$attrs/$listeners

    展开全文
  • Vue 组件通信例子

    2021-12-15 16:53:00
    使用两个组件, 一个组件ActiveUser显示姓名和年龄,另一个组件 UserData 可以修改这两项,如图所示: 工程里总共4个文件: main.js: import { createApp } from "vue"; import App from "./App.vue"; import ...
  • vue组件通信方式
  • 组件通信 一般常见的组件之间的通信有以下几种情况,A和B,B和C,B和D之间都是父子关系,C和D之间是兄弟组件关系。 常用的通信手段有两种: 1.ref:给元素或组件注册引用信息 2.children:访问父级组件和子组件...
  • 本文重点是梳理了前两个,父子组件通信和eventBus通信,我觉得Vue文档里的说明还是有一些简易,我自己第一遍是没看明白。 父子组件的通信 非父子组件的eventBus通信 利用本地缓存实现组件通信 Vuex通信 第一种通信...
  • Vue组件通信

    万次阅读 多人点赞 2022-07-20 09:38:56
    Vue组件通信
  • vue组件通信详解

    2020-03-01 11:54:55
    vue组件通信详解。 在使用vue开发的时候,组件是必不可少的一个环节,既然用到了组件,一定会有需要组件通信的时候,今天就来捡一捡组件通信的东西! vue组件通讯的方式有:父传子通信,子传父通信,bus中央事件总线...
  • 本质是通过派发事件然后监听事件从而更改值,(父子组件通信也可用这个方式,但是不同的一点就是父子组件通信的时候可以不用一个空的Vue实例来做中转,这种方式我这里就不做演示的了,因为我的题目是非父子通信) ...
  • vue组件通信的几种方式

    千次阅读 2021-04-24 20:39:22
    vue组件中的通信方式一共有3种,它们分别是: 1,父子组件通信 2,子父组件通信 3,兄弟组件通信 一,父子组件通信 //这是父组件 里面有一个num,把num传给子组件 <template> <div id="app"> &...
  • 当两个组件之间没有任何父子关系时,利用Vue标准的props传值和emit触发事件无法解决他们之间通信的问题。最近做的项目使用的是eventemitter2,来实现不相关组件之间的通信。这篇文章分享的是我对eventemitter2使用的...
  • 组件通信 几乎所有的mvvm框架中都要涉及组件通信的功能(吐槽一下knockout,毕竟是鼻祖就先不说它了)。而且目前的前端形式来看,组件化是一个项目的基础。所以选好一个合适的框架后,随着组件的不断增加,业务的...
  • 主要介绍了Vue组件通信$attrs、$listeners实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 如果想要了解非父子关系的组件传值,最好是在了解父传子和子传父的基础上在来了解非父子传值可能会有更透彻的思路。 因为非父子传值是通过定义事件总线来代理实现父传子+子传父从而实现的传值方式。 这是我总结的...
  • vue组件通信学习源码

    2022-04-06 12:15:31
    方便自己而已
  • vue-communication介绍他是一个可观测可调试的vue组件通信方案任意关系组件可直接通信支持跨组件监听数据变化支持发送离线数据兼容 IE由于该组件未生成dist,所以如果你的浏览器有兼容IE的需求,请在 vue.config.js ...
  • Vue中父组件到子组件通信主要由子组件的props属性实现。但是在一些情况下,父组件无法直接向子组件的props传值。比如子组件通过父组件的slot进入父组件,父组件根本不知道子组件是谁,更不用说用子组件的props了。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 48,473
精华内容 19,389
关键字:

vue组件通信