1.前言

由于在日常开发中会有一部分前端的开发任务,会涉及到Vue的项目的搭建、迭代、构建发布等操作,所以想系统的学习一下Vue相关的知识点,本专题会依照Vue的搭建、开发基础实践、进阶用法、打包部署的顺序进行记录。

历史文章链接如下:

本篇是VUE3组件的第二部分,主要包括以下内容:

  • 属性透传的作用和使用方式,与propsemits的区别
  • 依赖注入的作用和使用方式,与透传的区别
  • 如何使用组件插槽
  • 动态组件的使用

2.属性透传

有时候我们可能需要在组件中将一些 props 或者事件透传给其子组件或者父组件。透传的目的是为了让子组件直接访问父组件的属性或者方法,或者父组件能够直接访问子组件的属性或者方法,这种父子组件之间传递属性的方式就是透传,透传可以避免在中间层组件中重复定义这些属性或者方法,提高代码的可维护性和复用性。

在上一篇博客中聊到了可以用propsemits 来建立父子组件之间进行通信,其中,props 是父组件向子组件传递数据的方式,而 emits 则是子组件向父组件触发事件的方式,通过这两种方式交换数据其实也是一种特殊的透传方式。

严格意义上的透传,不需要使用propsemits来定义变量,这种透传形式常见于:classstyle事件监听器等。

以事件监听为例,在上一篇中提到过一个例子,有一个计数器的组件,每点击一次可以让父组件中的计数值+1,我们是通过defineEmits完成的子组件对父组件的调用。

通过事件的透传,可以直接在父组件上定义点击事件让子组件触发,代码如下:

  • 父组件:
    <template>
      <div class="hello">
        <Count @click="doIncr" />
        <Count @click="doIncr" />
    
        <div>父组件的计数:{{ count }}</div>
      </div>
    </template>
    
    <script setup>
    import Count from "../components/Count.vue";
    import { ref } from "vue";
    
    const count = ref(0);
    
    const doIncr = () => {
      count.value++;
    };
    </script>
    
  • 子组件:
    <template>
      <div>
        <button>点击 + 1</button>
      </div>
    </template>
    
    <script setup>
    
    </script>
    

从上面的代码可以看到,子组件里面几乎什么都没有做,就可以直接使用父组件的点击事件了,代码更加简洁。

3.依赖注入

依赖注入就是可以在组件树的任一一个组件节点中提供一些需要传递的属性,在这个节点的任一子节点中都可以将这些属性注入使用,例如在下面这个组件树中,我想在最上面的Parent节点中提供一些属性,在最下面的child中使用就可以使用依赖注入的方式。
【Vue3实践】(四)优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件-LMLPHP

有了透传为什么还需要依赖注入呢?
透传的方式是需要在组件树中一级一级的向下传递,中间节点其实并不关系这个需要传递的属性,这样就消耗了额外的性能。
其次,在传递的过程中如果被中间的某个节点通过定义props消费掉,那这个消费的节点也需要按照再次通过props传递到子节点中,才能继续往下传递。

这时候就突出了依赖注入的价值,我只需要在parent中通过provide函数定义需要传递的属性,然后在需要使用的组件节点中使用inject注入属性即可。

  • 父组件:提供一个对象
    <template>
      <div class="hello">
        <InjectChild />
      </div>
    </template>
    
    <script setup>
    import { provide } from "vue";
    import InjectChild from "../components/InjectChild.vue";
    
    provide("injectData", { id: 1, name: "挥之以墨" });
    </script>
    
  • 子组件:在视图中显示注入的对象
    <template>
      <div>
        注入的属性为:{{ data }}
      </div>
    </template>
    
    <script setup>
    import { inject } from "vue";
    
    const data = inject("injectData");
    </script>
    

【Vue3实践】(四)优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件-LMLPHP

4.组件插槽(slot)

在之前的例子中,父组件引入子组件都是完整的引用了子组件的整个<template>内容。有时候,不同的父组件引入子组件的时候,可能会针对子组件中的某一部分dom做定制化,简单的说,就是可以在引入子组件的中,父组件可以插入一些自己的dom元素。

插槽是一个很形象的概念,子组件提供了一个块供父组件自定义的区域<slot></solt>,父组件引用的时候,就会使用自定义dom元素传递到slot中。

slot 可以分为两种类型:默认插槽具名插槽

  • 默认插槽:一个组件中只能定义一个,父组件中的DOM会默认传递到这个插槽中。
  • 具名插槽:带有标识符的插槽,父组件可以使用v-slot:标识符的形式,将DOM传递到指定的插槽中去。

4.1.使用默认插槽

子组件在需要插入父组件元素的位置,使用<slot></slot>即可。

<template>
  <div>
    <slot></slot>
  </div>
</template>

父组件中:

<template>
  <SlotDemo>
    <p>默认插槽的内容</p>
  </SlotDemo>
</template>

<script setup>
import SlotDemo from "../components/SlotDemo.vue";
</script>

4.2.使用具名插槽

想对比与默认插槽,我们使用的更多的是具名插槽,这种类型的插槽不仅仅可以在子组件中定义多个,还可以根据需要灵活的将内容插入到想要插入的插槽中去。下面是与默认插槽使用上的区别。

在子组件中,可以给slot标签分配name属性来定义标识符:

<template>
  <div>
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>

父组件中通过v-slot:标识符(也可以简写为#标识符)来指定需要插入的插槽,与默认插槽不同的是,指定插入某个具名插槽的DOM元素需要被<template></template>包裹起来。

<template>
  <SlotDemo>
    <template v-slot:header>
      <p>具名插槽header的内容</p>
    </template>
    
    <p>默认插槽的内容</p>
    
    <template #footer>
      <p>具名插槽footer的内容</p>
    </template>
  </SlotDemo>
</template>

<script setup>
import SlotDemo from "../components/SlotDemo.vue";
</script>

【Vue3实践】(四)优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件-LMLPHP

4.3.Element Plus中的dialog例子

下图是一个在Element Plus找的一个弹窗的示例代码,在学习了插槽的内容之后,就很容易理解下面代码的含义了。

【Vue3实践】(四)优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件-LMLPHP

5.动态组件

所谓的动态组件,就是父组件的DOM标签中不显式的指定需要引入哪一个子组件,而是通过动态切换组件名的形式来选择加载哪一个组件,在开发中常常运用于使用TAB切换页面。

动态组件是通过<component :is="组件名" />的方式进行引入的:

  • 两个子组件
    <!--DynamicA-->
    <template>
      <div>
        <p>这是动态组件A</p>
      </div>
    </template>
    <!--DynamicB-->
    <template>
      <div>
        <p>这是动态组件B</p>
      </div>
    </template>
    
  • 父组件引入
    <template>
      <div class="hello">
        <label><input type="radio" v-model="currentComponent" :value="DynamicA" />组件A</label>
        <label><input type="radio" v-model="currentComponent" :value="DynamicB" />组件B</label>
        <component :is="currentComponent"></component>
      </div>
    </template>
    
    <script setup>
    import { shallowRef } from "vue";
    import DynamicA from "../components/DynamicA.vue";
    import DynamicB from "../components/DynamicB.vue";
    
    const currentComponent = shallowRef(DynamicA);
    </script>
    

【Vue3实践】(四)优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件-LMLPHP

这样就可以通过单选框动态切换了,但是现在存在一个问题,组件被切换后会被卸载掉,重新切换回来时会初始化一个新的组件,之前在该组件上的操作就消失了。

如果想让组件在被切换后也保留操作的状态,可以使用KeepAlive将动态引入组件的标签包裹起来:

<KeepAlive>
  <component :is="currentComponent"></component>
</KeepAlive>

6.总结

本篇主要讲了VUE3中组件的其他几个特性:属性透传、依赖注入、组件插槽、动态组件

  • 属性透传:
    • 可以避免在中间层组件中重复定义这些属性或者方法
    • 在子组件中不需要定义propsemits来接收属性和事件
  • 依赖注入:
    • 更加灵活在子组件中使用父组件提供的属性或方法,即使中间跨越了很多层级
    • 与透传的区别在于,不需要中间层级的组件一级一级的透传属性
  • 组件插槽:
    • 用于插入父组件中的自定义DOM,分为默认插槽和具名插槽
    • 具名插槽使用name="xxx"来定义标识符,父组件通过v-slot:xxx#xxx将DOM插入指定的插槽中
    • 父组件使用具名插槽需要将DOM包裹在<template></template>标签中
  • 动态组件:
    • 动态组件通过<component :is="组件名"></component>引入子组件,这里的组件名可以动态的替换
    • 如果要保留被替换掉的组件,使其不被卸载,可以将<component>包裹在<KeepAlive>标签中
03-23 21:18