Vue基础-day04

一、组件通信

组件化开发,是肯定需要我们将组件之间相互联系起来的,否则每一个组件都是独立的个体,无法相互贯通成为一个整体。

所以接下来我开始介绍组件通信。

1.组件之间的关系

组件之间共有两种关系,即父子关系与兄弟关系(注意:这里只有上下级可以称作父子关系,爷孙关系要当作兄弟关系看待)

我用一个图做一个更为直接的演示:

在上图中,A与B和C为父子关系,B与C为兄弟关系,而A与D、G、H也均为兄弟关系,相信这样大家更为清楚。

2.父子之间通信

父---->子

接下来我将用代码演示:

App.vue:
<template>
  <div class="app-container">
    <h1 ref="myH1">App 根组件---{{ countFromSon }}</h1>
    <button @click="getDom">通过ref获取DOM元素</button>
    <hr/>

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->

      <!--
          父组件---》子组件
            1.在父组件中:使用子组件时,在子组件身上声明自定义属性
            2.在子组件内部,使用prop接收父组件的数据
      -->

     <Left :name="username"></Left>
     <Right></Right>
    </div>
  </div>
</template>

<script>
import Left from './components/Left.vue'
import Right from './components/Right.vue'

export default {
  data() {
    return {
      username: '刘左柱',
    }
  },
  methods: {
  },
  components: {
    Left,
    Right
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}

.box {
  display: flex;
}
</style>

Left.vue
<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <p>接收到的父组件的username为:{{ name }}</p>
  </div>
</template>

<script>
export default {
  props: ['name'],
  methods: {
  }
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

子---->父

App.vue
<template>
  <div class="app-container">
    <h1 ref="myH1">App 根组件---{{ countFromSon }}</h1>
    <button @click="getDom">通过ref获取DOM元素</button>
    <hr/>

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
          <!--子组件---》父组件
            1.在父组件中:使用子组件时,在子组件身上声明自定义事件,@自定义事件="事件处理函数"
                        声明事件处理函数,在事件处理函数中能够接收到子组件传递过来的数据
            2.在子组件内部,使用this.$emit('自定义事件名称',传递给父组件的参数1,传递给父组件的参数2,.....)
      -->

     <Left @changeCount="getCount"></Left>-->
     <Right></Right>-->
    </div>
  </div>
</template>

<script>
import Left from './components/Left.vue'
import Right from './components/Right.vue'

export default {
  data() {
    return {
      countFromSon: 0,
    }
  },
  methods: {
    getCount(count) {
      this.countFromSon = count
    },
  },
  components: {
    Left,
    Right
  }
}
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}

.box {
  display: flex;
}
</style>

Left.vue
<template>
  <div class="left-container">
    <h3>Left 组件----{{ count }}</h3>
    <button @click="add">+1</button>
  </div>
</template>

<script>

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    add() {
      this.count++
      this.$emit('changeCount', this.count)
    }
  }
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

3.兄弟之间通信

既然是兄弟关系,那么他们之间是没有一个直接联系的,所以我们需要一个第三方将两者联系起来。

这个桥梁就是EventBus

通过上面的父子通信我们可以发现,想要实现数据之间的通信,说到底其实只需要两件事:

1.在接收数据的组件中,注册一个自定义事件,用来接收数据

2.在发送数据的组件中,触发这个自定义事件,用来发送数据

而显然,这个注册和触发都需要第三方桥梁来完成,它的具体使用步骤为:

①:创建eventbus.js模块,并向外共享一个Vue的实例对象

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

③:在数据发送方,调用bus.$emit(‘事件名称’,要发送的数据)方法触发自定义事件

接下来来看代码吧!

src/utils/eventBus.js
//导入vue
import Vue from 'vue'
//导出new Vue()
export default new Vue()
Left.vue
<template>
  <div class="left-container">
    <h3>Left 组件----{{ count }}</h3>
    <button @click="add">+1</button>
  </div>
</template>

<script>
import eventBus from "../utils/eventBus";

export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    add() {
      this.count++
      //兄弟组件通信
      //eventBus.$emit('事件名称',要传递的数据)
      eventBus.$emit('share', this.count)
    }
  }
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

Right.vue
<template>
  <div class="right-container">
    <h3>Right 组件----{{ countFromBro }}</h3>
  </div>
</template>

<script>
import eventBus from "../utils/eventBus.js";

export default {
  data() {
    return {
      countFromBro: 0,
    }
  },
  created() {
    //兄弟通信
    //eventBus.$on('自定义事件名称',事件处理函数(接收到的参数)=>{})
    eventBus.$on('share', (count) => {
      this.countFromBro = count
    })
  },
}
</script>

<style lang="less">
.right-container {
  padding: 0 20px 20px;
  background-color: lightskyblue;
  min-height: 250px;
  flex: 1;
}
</style>

这样我们主要的组件通信就完成了。

二、ref引用

1.ref是什么

ref用来辅助开发者,获取DOM元素。

2.使用ref获取元素

App.vue
<button @click="getDom">通过ref获取DOM元素</button>
<button @click="getComp">通过ref获取实例</button>
export default {
  methods: {
    getDom() {
      //通过this.$refs.引用名称  来获取dom元素
      this.$refs.myH1.style.color = 'red'
    },
    getComp() {
      //获取实例
      console.log(this.$refs.left)
      //获取方法
      this.$refs.left.add()
    }
  }
}

3.小案例

接下来我们用ref实现一个小需求:使用input自动获取焦点

Right.vue
 <input ref="inputRef" type="text" v-if="isShow" @blur="isShow=false">
    <button v-else @click="showInput">显示输入框</button>
export default { 
  data() {
    return {
      isShow: false
    }
  },
methods: {
    showInput() {
      this.isShow = true
      //默认情况下,数据发生变化时,不能直接拿到最新的dom
      //$nextTick(cb) 方法中的 cb 回调函数,会在组件的 DOM 更新完成之后,再执行。从而能保证在 cb 回调函数里 面可以拿到最新的 DOM 元素。
      //使用场景:当数据变化后,想要拿到更新后的dom,需要调用this.$nextTick(cb)
      this.$nextTick(() => {
        //input输入框的dom元素身上有一个focus方法,调用这个方法可以让输入框聚焦
        this.$refs.inputRef.focus()
      })
    }
  }
}

三、动态组件

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

App.vue
 <component :is="compName"></component>
 <button @click="compName='Left'">显示Left组件</button>
    <button @click="compName='Right'">显示Right组件</button>
export default {
  data() {
    return {
      compName: 'Left'
    }
  }
},

但是我们可以发现,当我们隐藏其中一个组件显示另一个的时候,隐藏的组件实际上是被销毁的,而显示的组件将被重新渲染,这样无疑是不合理的,所以这里我们引入一个概念,即keep-live缓存组件

App.vue
<keep-alive include="Left,Right">
        <!--      component用于切换组件的显示与隐藏-->
        <!--      :is属性:用于指定要渲染的组件名-->
        <component :is="compName"></component>
      </keep-alive>
    </div>
    <button @click="compName='Left'">显示Left组件</button>
    <button @click="compName='Right'">显示Right组件</button>
export default {
  data() {
    return {
      compName: 'Left'
    }
  }
}

四、插槽

1、什么是插槽

在默认情况下,使用组件时,在组件的开始和结束标签中间传递的内容将会被丢弃,而插槽可以改善这种情况。

2、插槽的基本使用

在封装组件时,我们可以定义一个插槽,也就是预留一个坑位,用来接收使用组件时传递过来的内容,在使用组件时,可以在组件的开始和结束标签中间传递一段html内容,这段内容就会被渲染到组件中插槽的位置。

Left.vue
<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <!-- 渲染 Article 组件 -->
    <Article>
      <!--      <h3>咏鹅</h3>-->
      <!--      <i>文章的作者:骆宾王</i>-->
      <!--      具名插槽传递插槽内容时,旧语法:-->
      <!--      <h3 slot="title">咏鹅</h3>-->
      <!--      <i slot="author">文章的作者:骆宾王</i>-->

      <!--      具名插槽传递插槽内容时,新语法:-->
      <!--
          <template v-slot:插槽名字>
               <给插槽传递的内容></>给插槽传递的内容>
          <template>
      -->
      <!--      <template v-slot:title>-->
      <!--        <h3>咏鹅</h3>-->
      <!--      </template>-->
      <!--      <template v-slot:author>-->
      <!--        <i>文章的作者:骆宾王</i>-->
      <!--      </template>-->

      <!--      新语法的简写形式-->
      <!--      <template #title>-->
      <!--        <h3>咏鹅</h3>-->
      <!--      </template>-->
      <!--      <template #author>-->
      <!--        <i>文章的作者:骆宾王</i>-->
      <!--      </template>-->

      <!--      作用域插槽:当插槽内容想要使用组件内部的数据时,需要使用作用域插槽,新语法形式-->
      <!--      <template #title>-->
      <!--        <h3>咏鹅</h3>-->
      <!--      </template>-->
      <!--      <template #author="obj">-->
      <!--        <i>文章的作者:骆宾王,年龄是----{{obj.age}},性别是----;{{obj.gender}}</i>-->
      <!--      </template>-->

      <!--      作用域插槽,旧语法形式-->
      <h3 slot="title">咏鹅</h3>
      <i slot="author" slot-scope="obj">文章的作者:骆宾王,年龄是----{{ obj.age }},性别是----{{ obj.gender }}</i>

      <!--      default会传递给默认插槽-->
      <template #default>
        <p>11111</p>
        <p>22222</p>
        <p>33333</p>
        <p>44444</p>
        <p>55555</p>
      </template>
    </Article>
  </div>
</template>

<script>

export default {
  components: {
    data() {
      return {
        age: 30
      }
    }
  }
}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

Article.vue
<template>
  <div>
    <!--    <h3>文章的标题</h3>-->
    <!--    slot就是插槽,它会接收使用组件时,开始和结束标签中,-->
    <!--    带有名字的插槽就是具名插槽-->
    <slot name="title">
      <!--      当使用组件时,没有传递内容,就使用插槽的默认内容-->
      <h3>默认标题</h3>
    </slot>
    <div>
      <p>这是文章的段落1。</p>
      <p>这是文章的段落2。</p>
      <p>这是文章的段落3。</p>
      <p>这是文章的段落4。</p>
    </div>
    <h6>文章的作者: xxx</h6>
    <!--    所有传递出去的数据都会保存在一个对象中-->
    <slot name="author" :age="age" :gender="'男'"></slot>
    <slot></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: 20
    }
  }
}
</script>

<style>

</style>

Q.E.D.