vue组件之间的通信

709 阅读3分钟

组件

  • 组件(Component )是Vue.js最强大的功能之一
  • 特性 一个页面由不同的组件构成
  • 组件 可以扩展HTML元素,封装可重用的代码

组件注册

全局注册

  • 格式:Vue.component(“组件名称”,{} )第一个参数是标签名称,第二个参数是选项对象

  • 为什么要用短横线的方式使用组件,因为在html中对大小写不敏感(也就是不区分大小写)

  • 全局组件注册后,任何的Vue实例(组件 )都可以用

      <div id="app">
         <!--    
    		4、组件可以重复使用多次 
               data是一个函数由于作用域的关系,每个组件的数据互不干扰 
    	--> 
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
          <!-- 8、必须使用短横线的方式使用组件 -->
               <!-- 组件的名称相当于HTML标签 -->
         <hello-world></hello-world>
      </div>
    
    <script type="text/javascript">
        
        // 7、如果使用驼峰式命名组件,必须使用短横线的方式使用组件
         Vue.component('HelloWorld', {
          data: function(){
            return {
              msg: 'HelloWorld'
            }
          },
          template: '<div>{{msg}}</div>'
        });
        
        Vue.component('button-counter', {
          // 1、组件参数的data值必须是函数 
          // 同时这个函数要求返回一个对象  
          data: function(){
            return {
              count: 0
            }
          },
          //  2、组件模板必须是单个根元素 <div><div>
          //  3、组件模板的内容可以是模板字符串  
          template: `
            <div>
              <button @click="handle">点击了{{count}}次</button>
              <button>测试123</button>
    			#  6 在字符串模板中可以使用驼峰的方式使用组件	
    		   <HelloWorld></HelloWorld>
            </div>
          `,
            //在组件中也可以定义methods
          methods: {
            handle: function(){
              this.count += 2;
            }
          }
        })
        
        // 创建根实例
        var vm = new Vue({
          el: '#app',
          data: {
            sum:1
          }
        });
      </script>
    
  • 全局注册的注意事项

    • data必须为函数返回一个对象的结构 (data是函数时,在重复使用同一个组件时,由于每个组件都有自己的作用域,每个组件相互独立,互不响应)
    • 为什么data不能为对象,因为data如果是对象也就是引用数据类型 ,在重复使用同一个组件时,每一个组件的data都是同一个地址,使用data数据会相互影响,也就是浅拷贝。

局部注册

  • 局部组件只能在注册它的父组件(Vue实例)中使用 (如 <div id="app"> </div> 中使用 )

  • 如果在全局组件的模板字符串中使用,则会报错

      <div id="app">
          <my-component></my-component>
      </div>
    
    
    <script>
        // 定义组件的模板
        var Child = {
          template: '<div>A custom component!</div>'
        }
        new Vue({
            el:"#app"
          //局部注册组件  
          components: {
            // <my-component> 将只在父模板可用  一定要在实例上注册了才能在html文件中使用
            'my-component': Child
          }
        })
     </script>
    

Vue组件之间传值

父组件向子组件传值

  • 在子组件中使用父组件的值:在父组件身上定义属性并绑定要传递的值

  • 在子组件中使用props接收

  • props是单身数据流,只能用在父传子

  • :title加:与不加:的区别 ,如果不加引号title传递的值是字符串

    如果加引号title是一个动态的变量用来传递数据

      <div id="app">
        <div>{{pmsg}}</div>
         <!--1、menu-item  在 APP中嵌套着 故 menu-item   为  子组件      -->
         <!-- 给子组件传入一个静态的值 -->
        <menu-item title='来自父组件的值'></menu-item>
        <!-- ptitle是父组件中data的数据
    		  传的值可以是数字、对象、数组等等	-->
        <menu-item :title='ptitle' content='hello'></menu-item>
      </div>
    
      <script type="text/javascript">
        Vue.component('menu-item', {
          // 3、 子组件用属性props接收父组件传递过来的数据  
          props: ['title', 'content'],
          data: function() {
            return {
              msg: '子组件本身的数据'
            }
          },         //在模板中使用父组件传递过来的值
          template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
        });
        var vm = new Vue({
          el: '#app',
          data: {
            pmsg: '父组件中内容',
            ptitle: '动态绑定属性'
          }
        });
      </script>
    

父亲直接访问孩子:通过 ref 获取子组件

ref 有两个作用

  • 如果你把它作用到普通 HTML 标签上,则获取到的是 DOM
  • 如果你把它作用到组件标签上,则获取到的是组件实例

在使用组件的时候,添加 ref 属性:

<blog-post title="My journey with Vue" ref="post"></blog-post>
//然后使用$refs.postthis.$refs.post;
​

使用建议:不在万不得以的情况下,不要通过这种方式修改数据,如果滥用这种方式会导致数据管理的混乱。

子组件向父组件传值

  • 子组件用$emit() 触发事件,触发绑定在父组件的自定义事件(也就是$emit()的第一个参数)

  • $emit()的第一个参数为自定义事件名称,第二个参数为需要传递的数据

  • 父组件等待子组件触发事件

  • 子组件通过调用父组件的方法来完成数据的传递,本质就是调用函数传参

     <div id="app">
        <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
         <!-- 2 父组件用v-on 监听子组件的事件
    		这里 enlarge-text  是从 $emit 中的第一个参数对应 通过$event获取第二个参数,              handle 为对应的事件处理函数	
    	-->	
        <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
        /*
          子组件向父组件传值-携带参数
        */
        
        Vue.component('menu-item', {
          props: ['parr'],
          template: `
            <div>
              <ul>
                <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
              </ul>
    			###  1、子组件用$emit()触发事件
    			### 第一个参数为 自定义的事件名称   第二个参数为需要传递的数据  
              <button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
              <button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
            </div>
          `
        });
        var vm = new Vue({
          el: '#app',
          data: {
            pmsg: '父组件中内容',
            parr: ['apple','orange','banana'],
            fontSize: 10
          },
          methods: {
            handle: function(val){
              // 扩大字体大小
              this.fontSize += val;
            }
          }
        });
      </script>
    
    

子组件访问父组件方法

  1. 第一种方法是直接在子组件中通过this.$parent.event来调用父组件的方法
  2. 第二种方法是在子组件里用$emit向父组件触发一个事件,父组件监听这个事件就行了。
  // 父组件
   <template>
  <div>
    <child></child>
  </div>
</template>
<script>
  import child from '~/components/dam/child';
  export default {
    components: {
      child
    },
    methods: {
      fatherMethod() {
        console.log('测试');
      }
    }
  };
</script>       
   //子组件调用
  this.$parent.fatherMethod();

非父子组件 之间的通信

Events Bus

  • 示例 :b=>a通信
  1. 创建 src/utils/event-bus.js 并写入

    /**
     * 全局通信总线
     * 呼叫中心
     * 作用:可以让任何组件之间相互通信
     * 
     */import Vue from "vue";
    ​
    // 直接导出一个空的 Vue 实例
    export default new Vue();
    ​
    
  2. 使用 event-bus 通信

    在通信的 a 端初始化 created 的时候注册监听一个自定义事件:

    import eventBus from '@/utils/event-bus'export default {
      ...
      created () {
        // 参数1:一个字符串,自定义事件名称
        // 参数2:一个函数,事件发布以后就会调用
        eventBus.$on('自定义事件名称', () => {
          // 业务逻辑代码
        })
    ​
        // 如果有参数的话,就声明接收
        eventBus.$on('自定义事件名称', (arg) => {
          // 业务逻辑代码
        })
      }
    }
    
  3. 在通信的 b 端发布调用自定义事件

    import eventBus from '@/utils/event-bus'export default {
      ...
      methods: {
        // 在某个业务方法中
        test () {
          // 参数1:自定义事件名称,必须是订阅的名字一致
          eventBus.$emit('自定义事件名称')
    ​
          // 如果需要传递额外的数据参数,就从第2个参数开始传
          eventBus.$emit('自定义事件名称', 123)
        }
      }
    }
    
  4. 销毁监听事件

    beforeDestroy () {
     // 销毁监听事件
     this.$bus.$off('test')
     }
    

祖孙后代之间的传值

允许祖先组件向其子孙后代 传值

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}
​
// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

第二种写法(响影)

// 父级组件提供 'foo'
var Provider = {
  provide() {
    item: this,//将父组件的实例 传递给子组件
  },
  data(){
    return{
      foo:"bar"
    }
  }
}
​
// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.item.foo) // => "bar"
  }
  // ...
}

组件插槽

  • 组件的最大特性就是利用性,而用好插槽能大大提高组件的复用性

  • 父组件向子组件传递内容

    image-20210208102852378

匿名插槽

  <div id="app">
    <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  -->  
    <alert-box>有bug发生</alert-box>
    <alert-box>有一个警告</alert-box>
    <alert-box></alert-box>
  </div>

  <script type="text/javascript">
    /*
      组件插槽:父组件向子组件传递内容
    */
    Vue.component('alert-box', {
      template: `
        <div>
          <strong>ERROR:</strong>
		# 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
		# 插槽内可以包含任何模板代码,包括 HTML
          <slot>默认内容</slot>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>

具名插槽

  • 如slot标签上有属性name,则它是叫具名插槽,否则叫匿名插槽

  • 根据标签中的属性 slot='header',对应组件中的模板 <slot name='header'></slot> 进行匹配

  • 使用 <template slot='header'> </template>标签包裹可以传递多条内容并且template不会渲染到页面上

      <div id="app">
        <base-layout>
           <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
    				如果没有匹配到 则放到匿名的插槽中   --> 
          <p slot='header'>标题信息</p>
          <p>主要内容1</p>
          <p>主要内容2</p>
          <p slot='footer'>底部信息信息</p>
        </base-layout>
    
        <base-layout>
          <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->  
          <template slot='header'>
            <p>标题信息1</p>
            <p>标题信息2</p>
          </template>
          <p>主要内容1</p>
          <p>主要内容2</p>
          <template slot='footer'>
            <p>底部信息信息1</p>
            <p>底部信息信息2</p>
          </template>
        </base-layout>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
        /*
          具名插槽
        */
        Vue.component('base-layout', {
          template: `
            <div>
              <header>
    			###	1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
                <slot name='header'></slot>
              </header>
              <main>
                <slot></slot>
              </main>
              <footer>
    			###  注意点: 
    			###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
                <slot name='footer'></slot>
              </footer>
            </div>
          `
        });
        var vm = new Vue({
          el: '#app',
          data: {
            
          }
        });
      </script>
    </body>
    </html>
    
    

作用域插槽

  • 父组件对子组件的内容加工

  • 在父组件中使用slot-scope属性,接收子组件绑定的属性上的数据 ,然后进行加工。

      <div id="app">
        <!-- 
    		1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件,
    		但样式希望不一样 这个时候我们需要使用作用域插
    	-->  
        <fruit-list :list='list'>
           <!-- 2、 父组件中使用了<template>元素,而且包含slot-scope="slotProps",
    			slotProps在这里只是临时变量,可以任意写  
                          slotProps 是一个对象,包含着传递过来的所有数据
    		---> 	
            <!-- 用slot-scope接收子组件item属性上的数据,然后用v-if加工数据-->
          <template slot-scope='slotProps'> 
            <strong v-if='slotProps.info.id==3' class="current">
                {{slotProps.info.name}}		         
             </strong>
            <span v-else>{{slotProps.info.name}}</span>
          </template>
        </fruit-list>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
        /*
          作用域插槽
        */
        Vue.component('fruit-list', {
           //用props来接收父组件中的数据
          props: ['list'],
          template: `
            <div>
              <li :key='item.id' v-for='item in list'>
    			###  3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx",
    			###   插槽可以提供一个默认内容,如果父组件没有为这个插槽提供了内容,会显示默认的内容。
    					如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
                <slot :info='item'>{{item.name}}</slot>
              </li>
            </div>
          `
        });
        var vm = new Vue({
          el: '#app',
          data: {
            list: [{
              id: 1,
              name: 'apple'
            },{
              id: 2,
              name: 'orange'
            },{
              id: 3,
              name: 'banana'
            }]
          }
        });
      </script>
    </body>
    </html>
    
    

Vue-路由

「点赞、收藏和评论」

❤️关注+点赞+收藏+评论+转发❤️,鼓励笔者创作更好的文章,谢谢🙏大家。