由于在日常开发中会有一部分前端的开发任务,会涉及到Vue的项目的搭建、迭代、构建发布等操作,所以想系统的学习一下Vue相关的知识点,本专题会依照Vue的搭建、开发基础实践、进阶用法、打包部署的顺序进行记录。
历史文章链接如下:
《Vue3搭建、路由配置与基本语法》
《响应式变量、双向绑定、计算属性、监听器》
《优雅使用VUE3 组件特性:组件定义、组件注册、事件监听、双向绑定》
《优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件》
《组合式函数、环境变量、axios配置》
《打包部署、nginx配置、Docker镜像构建》
本篇是VUE3组件的第二部分,主要包括以下内容:
props
和emits
的区别有时候我们可能需要在组件中将一些 props
或者事件透传给其子组件或者父组件。透传的目的是为了让子组件直接访问父组件的属性或者方法,或者父组件能够直接访问子组件的属性或者方法,这种父子组件之间传递属性的方式就是透传,透传可以避免在中间层组件中重复定义这些属性或者方法,提高代码的可维护性和复用性。
在上一篇博客中聊到了可以用props
和 emits
来建立父子组件之间进行通信,其中,props
是父组件向子组件传递数据的方式,而 emits
则是子组件向父组件触发事件的方式,通过这两种方式交换数据其实也是一种特殊的透传方式。
严格意义上的透传,不需要使用props
和emits
来定义变量,这种透传形式常见于:class
、style
、事件监听器
等。
以事件监听为例,在上一篇中提到过一个例子,有一个计数器的组件,每点击一次可以让父组件中的计数值+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>点击 + 1button>
div>
template>
<script setup>
script>
从上面的代码可以看到,子组件里面几乎什么都没有做,就可以直接使用父组件的点击事件了,代码更加简洁。
依赖注入就是可以在组件树的任一一个组件节点中提供一些需要传递的属性,在这个节点的任一子节点中都可以将这些属性注入使用,例如在下面这个组件树中,我想在最上面的Parent节点中提供一些属性,在最下面的child中使用就可以使用依赖注入的方式。
有了透传为什么还需要依赖注入呢?
透传的方式是需要在组件树中一级一级的向下传递,中间节点其实并不关系这个需要传递的属性,这样就消耗了额外的性能。
其次,在传递的过程中如果被中间的某个节点通过定义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>
在之前的例子中,父组件引入子组件都是完整的引用了子组件的整个内容。有时候,不同的父组件引入子组件的时候,可能会针对子组件中的某一部分dom做定制化,简单的说,就是可以在引入子组件的中,父组件可以插入一些自己的dom元素。
插槽是一个很形象的概念,子组件提供了一个块供父组件自定义的区域
,父组件引用的时候,就会使用自定义dom元素传递到slot
中。
slot
可以分为两种类型:默认插槽和具名插槽。
v-slot:标识符
的形式,将DOM传递到指定的插槽中去。子组件在需要插入父组件元素的位置,使用
即可。
<template>
<div>
<slot>slot>
div>
template>
父组件中:
<template>
<SlotDemo>
<p>默认插槽的内容p>
SlotDemo>
template>
<script setup>
import SlotDemo from "../components/SlotDemo.vue";
script>
想对比与默认插槽,我们使用的更多的是具名插槽,这种类型的插槽不仅仅可以在子组件中定义多个,还可以根据需要灵活的将内容插入到想要插入的插槽中去。下面是与默认插槽使用上的区别。
在子组件中,可以给slot
标签分配name属性来定义标识符:
<template>
<div>
<slot name="header">slot>
<slot>slot>
<slot name="footer">slot>
div>
template>
父组件中通过v-slot:标识符
(也可以简写为#标识符
)来指定需要插入的插槽,与默认插槽不同的是,指定插入某个具名插槽的DOM元素需要被包裹起来。
<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>
下图是一个在Element Plus找的一个弹窗的示例代码,在学习了插槽的内容之后,就很容易理解下面代码的含义了。
所谓的动态组件,就是父组件的DOM标签中不显式的指定需要引入哪一个子组件,而是通过动态切换组件名的形式来选择加载哪一个组件,在开发中常常运用于使用TAB切换页面。
动态组件是通过
的方式进行引入的:
<template>
<div>
<p>这是动态组件Ap>
div>
template>
<template>
<div>
<p>这是动态组件Bp>
div>
template>
<template>
<div class="hello">
<label><input type="radio" v-model="currentComponent" :value="DynamicA" />组件Alabel>
<label><input type="radio" v-model="currentComponent" :value="DynamicB" />组件Blabel>
<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>
这样就可以通过单选框动态切换了,但是现在存在一个问题,组件被切换后会被卸载掉,重新切换回来时会初始化一个新的组件,之前在该组件上的操作就消失了。
如果想让组件在被切换后也保留操作的状态,可以使用KeepAlive
将动态引入组件的标签包裹起来:
<KeepAlive>
<component :is="currentComponent">component>
KeepAlive>
本篇主要讲了VUE3中组件的其他几个特性:属性透传、依赖注入、组件插槽、动态组件
props
和emits
来接收属性和事件name="xxx"
来定义标识符,父组件通过v-slot:xxx
或#xxx
将DOM插入指定的插槽中
标签中
引入子组件,这里的组件名可以动态的替换
包裹在
标签中