携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第21天,点击查看活动详情
生命周期函数
每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程, 而这一系列的过程就被称之为组件的生命周期
在这个过程中的某一个阶段,我们可能会想要添加一些属于自己的代码逻辑,为此vue给我们提供了对应的生命周期钩子
生命周期函数是一些钩子函数(回调函数),在某个时间会被Vue源码内部进行回调
通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段
那么我们就可以在该生命周期中编写属于自己的逻辑代码
refs
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例
这个时候,我们可以给元素或者组件绑定一个ref的attribute属性
在组件对象中有一个名为$refs的属性,其值是一个对象
在该对象中持有注册过 ref attribute 的所有 DOM 元素和组件实例
<template>
<div>
<h2 ref="h2">this is h2 element</h2>
<Child ref="child" />
</div>
</template>
<script>
import Child from './components/Child'
export default {
components: {
Child
},
// ref获取到的一般是组件的原生dom对象
// 所以只有组件或对应元素被挂载后,才会存在对应的dom对象
mounted() {
// 如果是元素,那么获取到的就是对应的原生DOM
console.log(this.$refs.h2)
// 如果是一个组件,那么获取到的就是对应的组件实例对象
console.log(this.$refs.child)
// 在该组件实例对象上有一个属性$el
// 该属性的值
// 1. 在组件有且仅有一个根元素的情况下,值是组件根元素所对应的原生dom对象
// 2. 如果组件有多个根元素,vue会将子组件作为一个个node节点来处理
// 所以获取到的值为第一个node节点,一般为#text
// 所以如果要获取到第一个真正的元素节点需要调用其nextElementSibling属性
// 因此,虽然vue3支持在一个组件中拥有多个根元素
// 但是为了维护和使用方便,依旧推荐一个组件有且只能拥有一个根组件
console.log(this.$refs.child.$el)
}
}
</script>
<template>
<Infos :ref="getSectionRef" />
<Facility :ref="getSectionRef" />
<Landlord :ref="getSectionRef" />
</template>
<script setup>
// 当ref绑定的不是字符串,而是函数的时候
// vue会自动调用函数,并把对应元素或组件实例作为函数的参数传入
const getSectionRef = value => {
// 挂载完成后会执行该回调
// 卸载后也会执行该回调
if (!value) return
// 使用这种方式可以方便我们批量获取对应元素
// 对于组件实例 获取组件的根元素的原生dom元素可以通过$el属性
sectionEls.value[name] = value.$el
}
}
</script>
$parent 和 $root
| 属性 | 说明 |
|---|---|
| $parent | 获取当前组件的父组件 |
| $root | 获取当前组件树的根组件,也就是App实例对象 |
动态组件
<!--
动态组件是使用 component 组件
通过一个特殊的属性 -> is 来设置该内置组件需要展示的那个组件
-->
<!--
is的值为属性名称,只要是任何的合法的组件名都是可以被正常识别的
即cpnName的值 可以是 myCpn, my-cpn 和 MyCpn 中的任意一个
动态组件一般需需要在多个组件之间进行切换展示的时候使用
所以其is一般应该绑定的是一个变量
-->
<component :is="cpnName" />
<!-- 在动态组件上传递props 和 进行事件绑定 的方式 和普通组件是一致的 -->
<component name="Klaus" @getName="getName" :is="cpnName" />
keep-alive
对于动态组件,在切换组件后,原本的组件会被销毁,新的组件会被创建
如果频繁切换的时候,会比较损耗性能,且因为原本组件已经被销毁,所以原本组件的状态也会被重置
在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:keep-alive
| 属性 | 可选值 | 说明 | ||
|---|---|---|---|---|
| include | `string | RegExp | Array` | 只有名称匹配的组件会被缓存 |
| exclude | `string | RegExp | Array` | 任何名称匹配的组件都不会被缓存 |
| max | `number | string` | 最多可以缓存多少组件实例,一旦达到这个数 字,那么缓存组件中最近没有被访问的实例会被销毁 |
include和exclude二者都可以用逗号分隔字符串、正则表达式或一个数组来表示
如果使用逗号分隔的字符串来缓存多个组件的时候,不能添加对应的空格
keep-alive在缓存组件的时候,查找的不是引入的组件名,而是对应组件实例的name属性
也就是说组件Options Api中的name选项,该选项除了可以被keep-alive使用外
也可以在vue devTool中标识对应组件的名称,以方便进行调试
对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的(除第一次进入和被销毁的时候外)
但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件
这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听
这两个生命周期主要缓存组件显示和隐藏切换的时候就会被触发,即使该组件是刚刚被创建或者正在被销毁
也就是说这两个生命周期函数可能会和created和unmounted这两个生命周期钩子一起被回调
异步组件
默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么打包工具在打包时就会将组件模块打包到一起
即所有的自己的业务逻辑会被打包到一个名为app.js的文件中,所有的外部依赖,也就是那些第三方库会被打包会被单独打包到vendor.js中
这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢
所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js,例如那些不需要在首屏进行渲染的内容, 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容, 也就是说为的按需引入
默认情况下,当我们使用import函数异步引入的模块,webpack等打包工具在对这类模块进行打包的过程中,就会单独形成一个独立的js文件,这个过程就被称之为分包
所以如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数: defineAsyncComponent
defineAsyncComponent
<template>
<div>
<Cpn />
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
// 写法一 -> 参数是一个返回对应Promise的工厂函数
// 所谓工厂函数 就是那些专门用于返回特定实例的函数
// 在import函数中引入组件也是可以省略后缀的
// 因为import函数和import关键字的解析规则本质上是一致的
const Cpn = defineAsyncComponent(() => import('./components/Cpn'))
export default {
components: {
Cpn
}
}
</script>
// defineAsyncComponent 参数二 - 对应的配置对象
const Home = defineAsyncComponent({
// 实际需要加载的组件
loader: () => import('./components/Home.vue'),
// 异步组件加载过程中显示的组件
loadingComponent: Loader,
// 组件加载错误的时候显示的组件
errorComponent: Error,
// 延迟2s后,再显示loadingComponent
delay: 2000,
// 单位ms,如果在timeout中所设置的时间之内组件还没有被下载下来
// 无论是否报错,直接认定为失败
// 默认值是 Infinity
timeout: 0
})
但是,实际开发中,我们可能并不需要配置那么多的属性,为此vue提供了个内置组件suspense
Suspense是一个内置的全局组件,该组件有两个插槽
- default:如果default可以显示,那么显示default的内容
- fallback:如果default无法显示,那么会显示fallback插槽的内容
<suspense>
<template #default>
<About />
</template>
<!--
如果默认插槽中的内容无法显示(error的时候)或者About组件还在加载的时候(loading的时候)
会显示fallback插槽中的内容
-->
<template #fallback>
<!--
这里只是演示,所以也放置了异步组件,实际开发中,推荐在fallback插槽中放置同步组件
-->
<Home />
</template>
</suspense>
组件v-model
当我们在组件上使用v-model的时候
默认情况下的v-model其实是绑定了 modelValue 属性和 @update:modelValue的事件
我们可以给v-model传入一个参数,那么这个参数的名称就是我们绑定属性的名称
例如: v-model:title 等价于 1. 绑定了title属性 2. 监听了 @update:title的事件
父组件
<template>
<div>
<!--
// 默认情况下,组件上的v-model传递给子组件的时候,对应的prop名为modelValue
// 自定义事件的$event本质上就是子组件在调用@update:modelValue的时候传入的参数值
<Cpn v-model="count" /> === <Cpn :modelValue="count" @update:modelValue="count = $event" />
// 也可以自己指定所传递的prop的名称 -> 通过v-model:[propName]的形式
<Cpn v-model:count="count" /> === <Cpn :count="count" @update:count="count = $event" />
-->
<Cpn v-model:count="count" />
<div>{{ count }}</div>
</div>
</template>
<script>
import Cpn from './components/Cpn'
export default {
components: {
Cpn
},
data() {
return {
count: 0
}
}
}
</script>
子组件
<template>
<div>
<input type="text" v-model="count">
</div>
</template>
<script>
export default {
emits: ['update:count'],
props: {
count: {
type: [String, Number]
}
},
watch: {
count(v) {
this.$emit('update:count', v)
}
}
}
</script>
Mixin
组件和组件之间有时候会存在相同的代码逻辑,我们希望对相同的代码逻辑进行抽取的时候,就可以使用mixin选项
mixin选项只要是用于抽取options api中的相关逻辑代码,在compositions api中使用较少
- Mixin提供了一种非常灵活的方式,来分发Vue组件中的可复用功能
- 一个Mixin对象可以包含任何组件选项
- 当组件使用Mixin对象时,所有Mixin对象的选项将被 混合 进入该组件本身的选项中
mixin
// 导出一个对象 该对象中的属性和options api中的可配置选项都是一致的
// 在mixin的时候,会执行类似于 Object.assign(mixin中选项,组件options选项)
// 所以在冲突的时候,会优先保留组件内部选项,而覆盖mixin中的选项
// 而对于生命周期函数而言,组件内部的对应生命周期函数和mixin中的对应的生命周期函数 会合并成一个数组
// 在执行对应生命周期的函数的时候,会迭代对应数组并依次执行其中方法
// 一般先执行mixin中的对应生命周期函数,在执行组件中的对应生命周期函数
export default {
data() {
return {
msg: 'message in mixin'
}
},
created() {
console.log('created in mixin')
}
}
组件
<template>
<div>
{{ msg }}
</div>
</template>
<script>
// 引入mixin
import cpnMixin from '/src/mixin/cpnMixin'
export default {
// 使用mixins选项 进行 混入(注入)
mixins: [cpnMixin],
created() {
console.log('create in cpn')
}
}
</script>
冲突合并
- 如果是data函数的返回值对象
- 返回值对象默认情况下会进行合并
- 如果data返回值对象的属性发生了冲突,那么会保留组件自身的数据
- 生命周期钩子函数
- 生命周期的钩子函数会被合并到数组中,依次被调用
- 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象
- 比如都有methods选项,并且都定义了方法,那么它们都会生效
- 但是如果对象的key相同,那么会取组件对象的键值对
全局mixin
如果组件中的某些选项,是所有的组件都需要拥有的,那么这个时候我们可以使用全局的mixin
全局的Mixin可以使用 应用app的方法 mixin 来完成注册, 一旦注册,那么全局混入的选项将会影响每一个组件
app.mixin({
data() {
return {
msg: 'msg in global mixin'
}
},
created() {
console.log('created in global mixin')
}
})