动态组件
这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战
如果我们在页面上需要频繁通过v-if来判断需要渲染什么的组件的时候
如果条件过多,那么模板逻辑是十分繁琐的,此时vue提供了动态组件来简化对应的操作
动态组件是使用 component 组件,通过一个特殊的attribute is来实现
在动态组件上的传值和监听事件和普通组件是一模一样的
<template>
<div>
<button
v-for="tab in tabs"
:key="tab"
:class="{ active: activeTab === tab }"
@click="changeTab(tab)"
>
{{ tab }}
</button>
<!--
动态组件 is的值是需要显示的组件名(是那些被注册过的组件名)
注意: 这里is的值和组件名的使用规则是一致的
所以即使activeTab的值为home,注册的组件名为Home,其依旧可以匹配上对应的组件
-->
<component :is="activeTab" />
</div>
</template>
<script>
import About from './components/About.vue'
import Home from './components/Home.vue'
import Category from './components/Category.vue'
export default {
name: 'App',
components: {
About,
Home,
Category
},
data: () => ({
tabs: [
'home',
'about',
'category'
],
activeTab: 'home'
}),
methods: {
changeTab(v) {
this.activeTab = v
}
}
}
</script>
<style scoped>
.active {
color: red;
}
</style>
keep-alive
在因为默认情况下,我们在切换组件后,旧的组件会被销毁掉,再次回来时会重新创建组件
但是,在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉
这个时候我们就可以使用一个内置组件: keep-alive
<keep-alive>
<!-- 此时在组件切换的时候,旧的组件会被缓存下来 -->
<component :is="activeTab" />
</keep-alive>
但是这么操作的时候,所有的组件都会被缓存下来,有的时候,我们只希望有部分的组件会被缓存下来
此时可以通过给keep-alive设置属性的方式来解决
| 属性 | 取值 | 说明 | ||
|---|---|---|---|---|
| include | `string | RegExp | Array` | 只有名称匹配的组件会被缓存 |
| exclude | `string | RegExp | Array` | 任何名称匹配的组件都不会被缓存 如果exclude的值和include的值冲突,exclude的优先级更高 |
| max | `number | string` | 最多可以缓存多少组件实例,一旦达到这个数字, 那么缓存组件中最近没有被访问的实例会被销毁 |
<!--
使用字符串的时候,多个组件之间使用逗号划分
注意: 这里的值取的不是组件的名称,而是组件中的name选项的值(★★★)
-->
<keep-alive include="Home,Category">
<component :is="activeTab" />
</keep-alive>
<!-- 使用正则 -->
<keep-alive :include="/Home|Category/">
<component :is="activeTab" />
</keep-alive>
<!-- 使用数组 -->
<keep-alive :include="['Home', 'Category']">
<component :is="activeTab" />
</keep-alive>
异步组件
默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将组 件模块打包到一起
这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢
所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js
为此,Vue中给我们提供了一个函数:defineAsyncComponent
defineAsyncComponent接受两种类型的参数:
类型一: 工厂函数(用于创建组件的函数),该工厂函数需要返回一个Promise对象
<template>
<div>
<Home />
<About />
</div>
</template>
<script>
import { defineAsyncComponent } from 'vue'
// 异步加载对应的组件,直接在script中加载的目的,是为了可以在components中进行注册
// defineAsyncComponent 返回的结果就是我们所需要使用的组件 ---- 异步组件在进行打包的时候会被分包,形成对应的chunk_[hash值].js文件
const Home = defineAsyncComponent(() => import('./components/Home.vue'))
const About = defineAsyncComponent(() => import('./components/About.vue'))
export default {
name: 'App',
components: {
Home,
About
}
}
</script>
类型二: 接受一个对象类型,对异步函数进行配置
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>
$refs
某些情况下,我们在组件中想要直接获取到元素对象或者子组件实例,这个时候,我们可以给元素或者组件绑定一个ref的attribute属性
但是在Vue开发中我们是不推荐直接进行DOM操作的
$refs 是一个对象Object,持有注册过 ref attribute 的所有 DOM 元素和组件实例
<!-- 在refs注册对应的组件 -->
<About ref="about" />
// 取值
mounted() {
console.log(this.$refs.title) // 原生元素获取的就是DOM对象
console.log(this.$refs.about) // 组件获取的是组件实例对象(proxy对象)
console.log(this.$refs.about.$el) // 如果需要获取组件的dom对象,需要取其属性$el的值
}
$parent和$root
| 属性 | 说明 |
|---|---|
$parent | 当前组件的父组件元素 |
$root | 根组件元素,一般指代的就是App组件 |
注意: 在Vue3中已经移除了**$children的属性**,所以不可以使用了
组件的v-model
我们在input中可以使用v-model来完成双向绑定
现在封装了一个组件,其他地方在使用这个组件时,在组件上也是支持v-model
做法类似于vue2中的.sync操作符
父组件
<template>
<div>
<Home v-model="msg" />
<!-- 上面的写法等价于 -->
<!-- 所以实际传递给子组件的是modelValue,不是msg -->
<!--
update:modelValue是vue中的一个自定义的事件
子组件中更新的值,会直接作为事件参数被传递过来
注意: $event直接获取到的就是对应的值,不需要在去取target的value属性
-->
<Home :model-value="msg" @update:modelValue="msg = $event" />
<h2>{{ msg }}</h2>
</div>
</template>
<script>
import Home from './components/Home.vue'
export default {
name: 'App',
components: {
Home
},
data() {
return {
msg: '123'
}
}
}
</script>
子组件
<template>
<div>
<input type="text" v-model="message">
</div>
</template>
<script>
export default {
name: 'Home',
props: {
// 实际传入的值是modelValue, 不是msg
modelValue: String
},
emits: ['update:modelValue'],
computed: {
// 因为我们不应该直接修改props属性
// 而且默认情况下,props是单向数据流,子组件修改props并不会影响父组件中对应的状态(引用类型状态除外)
// 所以我们可以使用一个计算属性来完成对应的操作 --- 本质就是对update:modelValue进行对应的代理操作
message: {
get() {
return this.modelValue
},
set(v) {
this.$emit('update:modelValue', v)
}
}
}
}
</script>
但是实际上我们在组件上可能需要多个属性都需要进行双向绑定,
此时就不可以单单使用v-model,而且传递过来的属性名不能都叫做modelValue
所以实际情况下,我们一般可以手动设置需要传递给子组件的props变量的名称
父组件
<template>
<div>
<!-- 使用msg替代原本默认的modelValue -->
<!--
此时如果需要设置多个双向绑定的值的时候
<Home v-model:foo="foo" v-model:bar="bar" />
-->
<Home v-model:msg="msg" />
<h2>{{ msg }}</h2>
</div>
</template>
<script>
import Home from './components/Home.vue'
export default {
name: 'App',
components: {
Home
},
data() {
return {
msg: '123'
}
}
}
</script>
子组件
<template>
<div>
<input type="text" v-model="message">
</div>
</template>
<script>
export default {
name: 'Home',
props: {
msg: String
},
emits: ['update:msg'],
computed: {
message: {
get() {
return this.msg
},
set(v) {
this.$emit('update:msg', v)
}
}
}
}
</script>
生命周期
每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程
在这个过程中的某一个阶段,用于可能会想要添加一些属于自己的代码逻辑
为此Vue给我们提供了组件的生命周期函数
生命周期函数
生命周期函数是一些钩子函数,在某个时间会被Vue源码内部进行回调
通过对生命周期函数的回调,我们可以知道目前组件正在经历什么阶段
那么我们就可以在该生命周期中编写属于自己的逻辑代码了
缓存组件的生命周期
对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数(第一次和最后一次除外)
但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件
这个时候我们可以使用activated 和 deactivated 这两个生命周期钩子函数来监听 --- 只有被keep-alive缓存了的组件才有这两个生命周期函数
activated() {
console.log('activated')
},
deactivated() {
console.log('deactivated')
}