vue组件、组件之间的通信

321 阅读3分钟

组件

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

组件注册

全局注册

  • 格式:Vue.component(“组件名称”, 导入的组件)第一个参数是组件名称(标签名),第二个参数要引入的组件

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

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

      <!-- App文件  -->  
    <div id="app">
         <!--    
        4、组件可以重复使用多次 
               data是一个函数由于作用域的关系,每个组件的数据互不干扰 
      --> 
          <!--使用组件两种写法。直接写文件名 DuttonCounter 或遇到大写加-     button-counter(常用) -->
        <button-counter></button-counter>
        <button-counter></button-counter>
        <button-counter></button-counter>
          <!-- 、必须使用短横线的方式使用组件 -->
               <!-- 组件的名称相当于HTML标签 -->
         <hello-world></hello-world>
      </div><!-- main.js文件  -->  
    <script >
      //@=src文件夹
      import HelloWorld from '@/components/HelloWorld.vue'
       import DuttonCounter from '@/components/DuttonCounter.vue'
       //、如果使用驼峰式命名组件,必须使用短横线的方式使用组件
         Vue.component('HelloWorld',HelloWorld );
        Vue.component('DuttonCounter',DuttonCounter ) 
      </script>
    
  • 全局注册的注意事项

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

局部注册

  • 局部组件只要将它引入其他组件就是子组件

  • 三个步骤都要写不能会报错

      <div id="app">
        
        <!-- 使用组件  -->  
          <my-component></my-component>
      </div><script>
      //1:导入文件
      import MyComponent from '@/component/MyComponent.vue'
       
     export default {
        mane:'app'
       //2:调用组件
        components:{
        MyComponent
    ​
        } 
    }   
        })
     </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>
    //引入
   import MenuItem from '@/components/MenuItem.vue'
    export default {
      name: 'App',
     data:()=({
       ptitle: 123
     }),
      components:{
        //调用
        MenuItem
       }
     }
  </script>
​
​
​

子组件接收数据

<template>
 <div>
   <!-- 接收完数据,直接使用-->
  <div >{{title}}  </div>
   <div>{{content}} </div>
  </div>
</template>
  <script>
  export default {
    //props 接收父组件数据,里面写父组件自定义的属性名可以是数组方式,也可以是对象方式
    // 数组方式
    //props:['title','content']
    // }
  
    //对象方式 可以写更多配置
    props:{
   title:{
     type: Numder//type 用来约束数据类型
     //多个数据类型写法type:[String, Numder]
     //充许写的类型有 String Numder Boolean Array Object
     dafault:1312//dafault默认值
      
    },
    content:{
    type:string 
     required:true//该项数据必须传值  
    }
    }
  </script>
​
​

props属性

  • Props只读取

  • props中的数据不能直接修改

  • 如果实在需要修改props中的数据,可以转存data中

    //数组
    props:['title','content']
    //对象
    props:{
       title:{
         type: Numder
         dafault:1312
          
        },
        content:{
        type:string 
         required:true
        }
    

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

ref 有两个作用

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

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

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

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

子组件向父组件传值

  • 子组件用$emit() 触发事件,触发绑定在父组件的自定义事件(也就是$emit()的第一个参数)
  • $emit()的第一个参数为自定义事件名称,第二个参数为需要传递的数据
  • 父组件等待子组件触发事件
  • 子组件通过调用父组件的方法来完成数据的传递,本质就是调用函数传参

子组件传递

<template>
<div >
<button @click='btn'>按钮 </button>
  <!-- $emit可以写到标签中,也可以写在methods-->
 <button @click='$emit('button','123')'>按钮 </button>
</div>
</template>
<script> 
export default {
  methods:{
    btn(){
      //调用$emit方法,触发props事件
  this.$emit('button','1323')
    }
  }
}  
</script>

父接收

<template>
 <!--父组件准备的事件名,必须和子组件触发事件一致  --> 
<my-left @button="grtLeft"></my-left>
 <!-- 可以使用在 methods内写处理函数--><my-left @button="val=>(username=val)"></my-left><!-- 也可以使用在标签内写处理函数方法 -->
</template>
<script>
  export default{
   data:()=>({
     username:'12'
   }),
   methods:{
       grtLeft(age){
   //事件处理函数,通过参数接收传递的值
   console.log(age)
​
 }
}
}
</script>
V-model的底层实现
  • 通过父传子,子传父来实现的
  • 注意自定义属性和自定义事件,一定要写,value ,input ,因为v-model,就是通过这两个名来写的
  • 父组件
<template>
<div>
  <!-- 使用v-model代替:value和@input -->
  <!-- <my-left v-model="age"> </my-left> -->
  <my-left :value="age" @input="val=>(age=val)"> </my-left>
    
  </div>
</template>
<script>
  export default{
    data()=>({
    age:'12'
  })
  }
</script>
  • 子组件
<template>
<div>
  
<input type="text" :vaule="vaule" @input="$emit('input',$event.target.value)"/> 
  </div>
</template>
<script>
  export default{
    props:['vaule']
  }
</script>
  • 改变子组件自定义propevent
<template>
<div>
  
<input type="text" :vaule="vau" @input="$emit('inp',$event.target.value)"/> 
  </div>
</template>
<script>
  export default{
    props:['vau']
  },
    //通过 model里面的prop改变接收的自定义属性
    //通过even来改变事件名
    model:{
     prop:'vau',
     even:'inp'
    }
</script>
  • 非父子组件通信

非父子组件通信:Event Bus

我们可以使用一个非常简单的 Event Bus 来解决这个问题:

event-bus.js:

//创建一个Vue实例对象
import Vue from 'vue'
export default new Vue();

数据接收调用bus.$on('事件名称','事件处理函数')方法注册一个自定义事件

import bus from '@/bus'
export default{
data:()=>({
  username:''
}),
  //自定义事件,
  created(){
    //share事件名称,处理函数
   bus.$on('share',val=>(this.username=val))
  }
}
  

发送数据调用bus.$emit('事件名',发送的数据)方法触发自定义事件

<template>
  <button @click="sen"> </button>
</template>
<script>
import bus from '@/bus'
  export default{
    methods:{
      sen(){
       bus.$emit('share','1213')
      }
  }
  }
</script>

兄弟之间的传递

  • 兄弟之间传递数据需要借助事件中心,通过事件中心传递数据

    • 提供事件中心 var hub=new Vue()
  • 传递数据方,通过hub.$emit(方法名,传递的数据 )事件触发兄弟的钩子函数

  • 接收数据方,通过mounted(){hub.$on(“方法名”, (接收传递过来的数据)=>{} }

  • 销毁事件, 通过hub.$off() 方法名销毁事件之后无法进行传递数据

     <div id="app">
        <div>父组件</div>
        <div>
          <button @click='handle'>销毁事件</button>
        </div>
        <test-tom></test-tom>
        <test-jerry></test-jerry>
      </div>
      <script type="text/javascript" src="js/vue.js"></script>
      <script type="text/javascript">
        /*
          兄弟组件之间数据传递
        */
        //1、 提供事件中心
        var hub = new Vue();
    ​
        Vue.component('test-tom', {
          data: function(){
            return {
              num: 0
            }
          },
          template: `
            <div>
              <div>TOM:{{num}}</div>
              <div>
                <button @click='handle'>点击</button>
              </div>
            </div>
          `,
          methods: {
            handle: function(){
              //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
              hub.$emit('jerry-event', 2);
            }
          },
          mounted: function() {
           // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名
            hub.$on('tom-event', (val) => {
              this.num += val;
            });
          }
        });
        Vue.component('test-jerry', {
          data: function(){
            return {
              num: 0
            }
          },
          template: `
            <div>
              <div>JERRY:{{num}}</div>
              <div>
                <button @click='handle'>点击</button>
              </div>
            </div>
          `,
          methods: {
            handle: function(){
              //2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件
              hub.$emit('tom-event', 1);
            }
          },
          mounted: function() {
            // 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名
            hub.$on('jerry-event', (val) => {
              this.num += val;
            });
          }
        });
        var vm = new Vue({
          el: '#app',
          data: {
            
          },
          methods: {
            handle: function(){
              //4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据  
              hub.$off('tom-event');
              hub.$off('jerry-event');
            }
          }
        });
      </script>

组件插槽

  • 组件的最大特性就是利用性,而用好插槽能大大提高组件的复用性
  • 父组件向子组件传递内容

匿名插槽

​
  <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在这里只是临时变量   
        --->  
            <!-- 用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>

组件样式

支持less和scss

  • 写法
<!--默认样式 css 写法 ,如果要改变样式写法可以使用 lang来指定写法 -->
<style lang="less"></style>
<!--或者  -->
<style lang="scss"></style>
  • 安装所需的包
# 使用less,需要安装下面的包
npm i less-loader@7.3.0 -D     #(@vue/cli 4.5版本需要指定版本安装)
npm i less-loader              #(@vue/cli 5.0.1,直接安装)# 使用scss,需要安装下面的包
npm i sass-loader@10  sass -D  #(@vue/cli 4.5版本需要指定版本安装)
npm i sass sass-loader -D      #(@vue/cli 5.0.1,直接安装)

样式冲突解决

<!-- 直接给组件添加 scoped 就可以,这样就可以给单独的组件设置样式,解决冲突-->
<style lang="less" scoped></style>
scoped的原理
  • 就是给单当前组件添加相同的自定义属性
  • 在通过当前组件全部样式,都通过属性选择器选择
<!--给当前组件都添加,相同的自定义属性 -->
<template>
  <div data-v-7ba5bd90>
    <p> 123</p>
    <div class="abc" data-v-7ba5bd90>这是一个div</div>
  </div>
</template>
<!-- 当前组件全部样式,都通过属性选择器选择-->
<style lang="less">
  p[data-v-7ba5bd90] {
    color: red;
  }
  .abc[data-v-7ba5bd90] {
    color: darkred;
  }
</style>

样式穿透

  • 用于父元组件改子组织的样式
//注意穿透不同样式写法也不同
<style scoped>
  // css穿透 .abc父元素,p子元素
  .abc >>> p{
    color:red;
  }
  //less穿透
    .abc/deep/p{
    color:red;
  }
  
    .abc::v-deep p{
    color:red;
  }s
<style>

动态类名

  • 动态类名 :class
  • 一个类名使用一个表达式就可以
  • 多个类名使用,数组格式
  • 使用复杂表达式控制类名
<template>
<div>
  <!--利用三元运算符判断,点击button来更改flag值,继续切换类名 -->
  <p :class="['a',flag ? 'thin':'']">1213</p>
  <button @click="flag=!flag">却换类名 </button>
  </div>
</template>
<script>
  export default{
    data:()=>({
      flag=true
    })
  }
</script>
<style>
  .a{
    color:red
  }
  .thin{
   color:yellow
  }
</style>

ref引用

普通元素的ref

  • Vue js中通过ref属性找到元素
  • 每个vue的组件实例上,都包含一个$refs对象,里面存储着对应的DOM元素或组件的引用
  • 默认情况下,组件的$refs指向一个空对象
<template>
  <div>
    <!-- metRef名字自定义,结尾最好加ref -->
  <input type="text" ref="metRef"/>
    <button @click="fn">点击获取值</button>
  </div>
</template>
<script>
  export default{
    methods:{
      fn(){
    console.log(this)//this里面有resf这个对象
    //通过this.$resf.gaeRef.value找到当前元素的值
    const val=this.$resf.gaeRef.value
    }     
   } 
  } 
</script>

组件的ref

  • 使用组件的时候也可以使用ref属性
  • 使用这个方法可以做很多事,比如获取子组件的数据和调用方法
<template>
  <div>
 <my-left ref='gaeRef'></my-left>
    <button @click="fn">点击获取值</button>
  </div>
</template>
<script>
  export default{
    methods:{
      fn(){
      //调用自组件的aeg数据项,前提是子组件必须有aeg这项数据
     const val=this.$resf.gaeRef.aeg
     //调用子组件的show方法
     this.$resf.gaeRef.show()
    
        
    }     
   } 
  } 
</script>

$nextTick()

  • $nextTick()作用将回调延迟到下一次DOM更新之后执行

    //在使用this.$refs.gaeRef,可能会出现找不到
    //因为在调用这个方法后,代码马上就执行了,还没有渲染到页面中,
    //我们需要等待DOM更新,更新完毕,才执行这个代码
    //所以可以用$nextTick(),将回调延迟到下次DOM更新之后执行
    methods:{
      getLeft(){
      this.flag=true
        
     this.$nextTick(()=>{
    this.$refs.gaeRef.show()
    })
    }
    }
    ​
    

动态组件

初步使用

动态组件指的是动态切换组件的显示隐藏

  • vue提供的一个内置组件,专门用来实现动态组件的渲染
<template>
<div>
  <component :is="com"></component>
  <div>
    <button @click="com=Myleft"> </button>
    <button @click="com=c"> </button>
  </div>
  </div>
</template>
<script>
  import Myleft from '@/components/Myleft.vue'
  import Myleft from '@/components/Mytop.vue'
  export default{
    data:()=>({
    com:Myleft
    }),
    components:{
     Myleft
     Mytop     
    }
  }  
</script>

保持组件状态

默认情况下,切换动态组件时无法保持组件的状态

  • 如有两个组件来回切换,其中一个改变了数据,但一切换组件又回到了初始状态

如果要保持组件状态可以使用vue内置的 组件保持动态组件的状态

  • 通过 include 属性指定那个组件需要被缓存(不指定就不会缓存)
<!--注意:如果需要缓存多个组件,多个组件用逗号分割,绝对不能加空格  --><keep-alive include="Mytop,Myleft">
  <component :is="com"></component>
</keep-alive>

\