Vue组件开发--ref引用、动态组件、组件缓存、异步组件、组件混入
非组合式API中的组件使用
一、$refs的使用
某些情况下,我们在组件中想要直接获取到DOM
元素对象或者子组件实例。
Vue
开发中并不推荐进行向原生js
一样操作DOM
元素,此时可以给DOM
元素或者组件绑定一个ref
属性。
当前组件实例有一个$refs
属性: 它是一个对象,对象中有注册过ref
属性的DOM
元素和组件实例。
1.1 代码示例
<template>
<div class="app">
<!-- 给h2、button中两个DOM元素绑定ref属性 -->
<h2 ref="title" class="title" :style="{ color: titleColor }">{{ message }}</h2>
<button ref="btn" @click="changeTitle">修改title</button>
<!-- 给banner组件绑定ref属性 -->
<banner ref="banner"/>
</div>
</template>
<script>
import Banner from "./Banner.vue"
export default {
components: {
Banner
},
data() {
return {
message: "Hello ref",
}
},
methods: {
changeTitle() {
// 1.获取h2/button 这种DOM元素
console.log(this.$refs.title)
console.log(this.$refs.btn)
// 2.获取banner组件: 组件实例
console.log(this.$refs.banner)
// 2.1.在父组件中可以主动的调用子组件的对象方法
this.$refs.banner.bannerClick()
// 2.2.获取banner组件实例, 获取banner中的DOM元素
console.log(this.$refs.banner.$el)
// 3.3.如果banner template是多个根节点, 拿到的是第一个node节点
// 这种情况很少遇到,一般不会去给组件设置多个根节点
console.log(this.$refs.banner.$el.nextElementSibling)
// 4.组件实例还有两个属性:
// 在Vue3中已经移除了$children的属性,不可以再使用了,这两个其实也不是很常用
console.log(this.$parent) // 获取父组件
console.log(this.$root) // 获取根组件
}
}
}
</script>
1.2 小结
- 在
template
中给想要获取的元素添加ref
属性。 - 在
Vue
当前组件下,使用this.$refs
去获取添加了ref
属性的元素。
二、动态组件
假设有两个按钮,点击一下就切换一个组件。
一般来讲会有两种方案去实现这个需求:
-
使用
v-if
或者v-show
来显示不同的组件 -
使用动态组件的方式
动态组件使用 component
组件,通过一个属性is
来实现组件切换
2.1 代码示例
<template>
<div class="app">
<div class="tabs">
<template v-for="(item, index) in tabs" :key="item">
<button :class="{ active: currentTab === item }"
@click="itemClick(item)">
{{ item }}
</button>
</template>
</div>
<div class="view">
<!-- 动态组件 component -->
<!-- is中的组件需要来自两个地方: 1.全局注册的组件 2.局部注册的组件 -->
<component name="张三" :age="18"
@homeClick="homeClick"
:is="currentTab">
</component>
</div>
</div>
</template>
<script>
import Home from './views/Home.vue'
import About from './views/About.vue'
import Category from './views/Category.vue'
export default {
components: {
Home,
About,
Category
},
data() {
return {
tabs: ["home", "about", "category"],
currentTab: "home"
}
},
methods: {
itemClick(tab) {
this.currentTab = tab
},
homeClick(payload) {
console.log("homeClick:", payload)
}
}
}
</script>
<style scoped>
.active {
color: red;
}
</style>
2.2 小结
- 动态组件使用
component
组件,通过一个属性is
来实现组件切换 is
中只需要传递组件的字符串名称。- 组件需要来自两个地方: 全局注册的组件或者局部注册的组件
- 动态组件也可以给它们传值和监听事件,只要将属性和监听事件放到
component
上来使用- 其它没有这些属性数据或者事件的不用管就可以了
三、组件缓存
<KeepAlive>
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。
简单的说,就是让一个组件保持存活状态。
默认情况下,一个组件实例在被替换掉后会被销毁,这会导致它丢失其中所有已变化的状态。
当这个组件再一次被显示时,会创建一个只带有初始状态的新实例。
而使用<KeepAlive>
这个内置组件包裹需要保存状态的动态组件就可以解决上面的问题。
3.1 KeepAlive基本使用
<KeepAlive>
<component :is="view" />
</KeepAlive>
3.2 KeepAlive的属性
-
include: 只有名称匹配的组件会被缓存
- 数据类型支持:字符串、正则表达式、数组
- 匹配首先检查组件自身的
name
选项
-
exclude: 任何名称匹配的组件都不会被缓存
- 数据类型支持:字符串、正则表达式、数组
- 匹配首先检查组件自身的
name
选项
-
max: 最大缓存实例数,达到最大值是缓存组件中最久没有被访问的缓存实例将被销毁
- 数据类型支持:字符串、数字
<!-- 以英文逗号分隔的字符串 -->
<KeepAlive include="a,b">
<component :is="view" />
</KeepAlive>
<!-- 正则表达式 (需使用 `v-bind`) -->
<KeepAlive :include="/a|b/">
<component :is="view" />
</KeepAlive>
<!-- 数组 (需使用 `v-bind`) -->
<KeepAlive :include="['a', 'b']">
<component :is="view" />
</KeepAlive>
<KeepAlive :max="10">
<component :is="activeComponent" />
</KeepAlive>
3.3 缓存实例的生命周期
对于缓存的组件来说,再次进入时,不会执行created
或者mounted
等生命周期函数的。
但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件。
这个时候可以使用activated
和 deactivated
这两个生命周期钩子函数来监听上述变化。
<script>
export default {
activated() {
// 在首次挂载、
// 以及每次从缓存中被重新插入的时候调用
},
deactivated() {
// 在从 DOM 上移除、进入缓存
// 以及组件卸载时调用
}
}
</script>
注意:
activated
在组件挂载时也会调用,并且deactivated
在组件卸载时也会调用。- 这两个钩子不仅适用于
<KeepAlive>
缓存的根组件,也适用于缓存树中的后代组件。
四、异步组件
异步组件和打包是息息相关的。了解它对项目的优化也是很有必要的。
4.1 webpack分包
1)js文件目录解释
可以看到,之前的所有自己编写的Vue
组件模块代码都被打包到了app.js
中。
chunk-vendors
字面意思是代码块供应商,其实就是项目中依赖得的各种第三包,也包括Vue
的源代码。
.map
文件,是为了方便调试用的。
因为打包后的代码已经无法直观的通过肉眼去看了。调试时,是不清楚到底哪行代码出现的问题。
而.map
文件就是一个把打包后的代码和源代码一一对应的映射文件,从而解决了上述问题。
2)这种代码方式带来的问题
随着项目的不断庞大,app.js
文件的内容过大,会造成首屏的渲染速度变慢。
3)代码分包
打包时,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js
。
这些chunk.js
会在需要时从服务器加载下来,并且运行代码,显示对应的内容。
这样就解决了app.js
文件过大的问题。
具体实现:
import("./utils/math").then(res => {
res.sum(20, 30)
})
import
函数可以让webpack
对导入文件进行分包处理。
这种方式是一种异步导入,返回的是一个promise
对象。
可以看到多了两个以749
开头的文件。这就是import
函数导入的异步组件,webpack
会对它进行分包处理。
4.2 Vue中异步组件
在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。
目的是可以对其进行分包处理。Vue
提供了defineAsyncComponent
方法来实现此功能。
defineAsyncComponent
接受两种类型的参数:
- 工厂函数:该工厂函数需要返回一个
Promise
对象 - 对象类型:对异步函数进行配置
1)工厂函数
ES
模块动态导入会返回一个Promise
,所以多数情况下我们会将它和 defineAsyncComponent
搭配使用。
类似 Vite
和 Webpack
这样的构建工具也支持此语法 ,并且会将它们作为打包时的代码分割点。
<script>
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
)
export default {
components: {
AsyncComp
},
}
</script>
2)对象类型
<script>
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
export default {
components: {
AsyncComp
},
}
</script>
这种写法实际用的比较少,了解即可。一般都会用路由懒加载的方式。
3)注意点
<script>
const Category = import("./views/Category.vue")
export default {
components: {
Category: AsyncCategory
}
}
</script>
可能有人会有疑问,为什么.vue
文件中不用import
函数进行导入。
因为import
函数进行导入返回的是一个Promise
对象。Promise
对象在components
中是无法使用的。
五、组件混入
使用组件化的方式在开发整个Vue
的应用程序,但是组件和组件之间有时候会存在相同的代码逻辑。
如果想要对相同的代码逻辑进行抽取,在Vue2
和Vue3
中都支持的一种方式就是使用Mixin
的方式来完成。
5.1 Mixin的作用如下:
-
Mixin
提供了一种非常灵活的方式,来分发Vue
组件中的可复用功能 -
一个
Mixin
对象可以包含任何组件选项 -
当组件使用
Mixin
对象时,所有Mixin
对象的选项将被 混合 进入该组件本身的选项中
5.2 局部混入代码示例
1)新建mixin.js存放想要抽取的代码逻辑
export default {
data() {
return {
message: "Hello World"
}
},
created() {
console.log("message:", this.message)
}
}
2)组件中混入mixin.js中的代码逻辑
<template>
<h2>组件A</h2>
</template>
<script>
// 引入抽取出来的代码逻辑
import messageMixin from '../mixins/mixin'
export default {
// 对messageMixin中的代码逻辑进行混入
mixins: [messageMixin]
}
</script>
<style scoped>
</style>
A
组件混入mixin.js
中的代码后,也就获得了mixin.js
里面封装的功能。
5.3 全局混入Mixin代码示例
在main.js
中使用app
对象的mixin
方法对所有组件进行代码混入。
const app = createApp(App)
// 这里面就是需要混入所有组件的内容
app.mixin({
created() {
console.log("mixin created")
}
})
app.mount('#app')
全局混入用的并不多,了解即可
5.4 Mixin对象中的选项和组件对象中的选项发生冲突的解决方式
-
data
函数的返回值对象冲突-
返回值对象默认情况下会进行合并
-
data
返回值对象的属性发生了冲突,那么会保留组件自身的数据
-
-
如何生命周期钩子函数冲突
- 生命周期的钩子函数会被合并到数组中,都会被调用
-
其它值为对象的选项,如
methods
、components
和directives
,将被合并为同一个对象。- 比如都有
methods
选项,并且都定义了方法,那么它们都会生效 - 但是如果对象的
key
相同,那么会取组件对象本身的键值对
- 比如都有
更多推荐
所有评论(0)