一. 动态组件
比如我们现在想要实现了一个功能:
- 点击一个tab-bar,切换不同的组件显示;
案例截图
这个案例我们可以通过两种不同的实现思路来实现:
- 方式一:通过v-if来判断,显示不同的组件;
- 方式二:动态组件的方式;
1.1. v-if显示不同组件
我们可以先通过v-if来判断显示不同的组件,这个可以使用我们之前讲过的知识来实现:
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active: currentTab === tab}" @click="tabClick(tab)"> {{tab}} </button> <template v-if="currentTab === 'home'"> <home></home> </template> <template v-else-if="currentTab === 'about'"> <about></about> </template> <template v-else> <category></category> </template> </div> </template> <script> import Home from "./pages/Home.vue"; import About from "./pages/About.vue"; import Category from "./pages/Category.vue"; export default { components: { Home, About, Category }, data() { return { tabs: ["home", "about", "category"], currentTab: "home" } }, methods: { tabClick(tab) { this.currentTab = tab; } } } </script> <style scoped> .active { color: red; } </style>
这里不再给出过多解释,都是之前讲过的内容。
1.2. 动态组件的实现
动态组件是使用 component
组件,通过一个特殊的attribute is
来实现:
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active: currentTab === tab}" @click="tabClick(tab)"> {{tab}} </button> <component :is="currentTab"></component> </div> </template>
这个currentTab的值需要是什么内容呢?
- 可以是通过component函数注册的组件;
- 在一个组件对象的components对象中注册的组件;
1.3. 动态组件的传值
如果是动态组件我们可以给它们传值和监听事件吗?
- 也是一样的;
- 只是我们需要将属性和监听事件放到component上来使用;
App.vue的代码如下:
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active: currentTab === tab}" @click="tabClick(tab)"> {{tab}} </button> <component name="why" :age="18" @pageClick="pageClick" :is="currentTab"/> </div> </template> <script> import Home from "./pages/Home.vue"; import About from "./pages/About.vue"; import Category from "./pages/Category.vue"; export default { components: { Home, About, Category }, data() { return { tabs: ["home", "about", "category"], currentTab: "home" } }, methods: { tabClick(tab) { this.currentTab = tab; }, pageClick(payload) { console.log("pageClick", payload); } } } </script> <style scoped> .active { color: red; } </style>
Home.vue中的代码如下:
<template> <div @click="pageClick"> Home组件: {{name}}-{{age}} </div> </template> <script> export default { props: { name: String, age: Number }, emits: ["pageClick"], methods: { pageClick() { this.$emit("pageClick", "Home组件"); } } } </script>
1.4. keep-alive使用
1.4.1. 认识keep-alive
我们先对之前的案例中About组件进行改造:
- 在其中增加了一个按钮,点击可以递增的功能;
<template> <div> About组件 <button @click="counter++">{{counter}}</button> </div> </template> <script> export default { data() { return { counter: 0 } } } </script>
比如我们将counter点到10,那么在切换到home再切换回来about时,状态是否可以保持呢?
- 答案是否定的;
- 这是因为默认情况下,我们在切换组件后,about组件会被销毁掉,再次回来时会重新创建组件;
但是,在开发中某些情况我们希望继续保持组件的状态,而不是销毁掉,这个时候我们就可以使用一个内置组件:keep-alive。
<keep-alive> <component name="why" :age="18" @pageClick="pageClick" :is="currentTab"/> </keep-alive>
1.4.2. keep-alive属性
keep-alive有一些属性
include
-string | RegExp | Array
。只有名称匹配的组件会被缓存;exclude
-string | RegExp | Array
。任何名称匹配的组件都不会被缓存;max
-number | string
。最多可以缓存多少组件实例,一旦达到这个数字,那么缓存组件中最近没有被访问的实例会被销毁;
include
和 exclude
prop 允许组件有条件地缓存:
- 二者都可以用逗号分隔字符串、正则表达式或一个数组来表示;
- 匹配首先检查组件自身的
name
选项;
- 如果
name
选项不可用,则匹配它的局部注册名称 (父组件components
选项的键值);
<!-- 逗号分隔字符串 --> <keep-alive include="a,b"> <component :is="view"></component> </keep-alive> <!-- regex (使用 `v-bind`) --> <keep-alive :include="/a|b/"> <component :is="view"></component> </keep-alive> <!-- Array (使用 `v-bind`) --> <keep-alive :include="['a', 'b']"> <component :is="view"></component> </keep-alive>
1.4.3. 缓存的生命周期
对于生命周期的知识下面的四有讲解,因为这部分知识和keep-alive联系紧密,所以放到了这里。
大家可以等学习了生命周期后,再回头看这部分的内容。
对于缓存的组件来说,再次进入时,我们是不会执行created或者mounted等生命周期函数的:
- 但是有时候我们确实希望监听到何时重新进入到了组件,何时离开了组件;
- 这个时候我们可以使用
activated
和deactivated
这两个生命周期钩子函数来监听;
<template> <div> About组件 <button @click="counter++">{{counter}}</button> </div> </template> <script> export default { name: "about", data() { return { counter: 0 } }, // 当重新进入活跃状态时会回调 activated() { console.log("about activated") }, // 当离开活跃状态时会回调 deactivated() { console.log("about deactivated") } } </script>
二. 异步组件
2.1. webpack的代码分包
默认的打包过程:
- 默认情况下,在构建整个组件树的过程中,因为组件和组件之间是通过模块化直接依赖的,那么webpack在打包时就会将
组件模块
打包到一起(比如一个app.js文件中); - 这个时候随着项目的不断庞大,app.js文件的内容过大,会造成首屏的渲染速度变慢;
打包时,代码的分包:
- 所以,对于一些不需要立即使用的组件,我们可以单独对它们进行拆分,拆分成一些小的代码块chunk.js;
- 这些chunk.js会在需要时从服务器加载下来,并且运行代码,显示对应的内容;
那么webpack中如何可以对代码进行分包呢?
默认情况下,我们直接使用import来依赖一个模块时,是不会进行分包的:
import {sum} from './utils/math'; console.log(sum(20, 30));
如果我们希望进行分包,那么可以使用import函数:
import("./utils/math").then(({ sum }) => { console.log(sum(20, 30)); });
import打包后的效果
2.2. vue中实现异步组件
如果我们的项目过大了,对于某些组件我们希望通过异步的方式来进行加载(目的是可以对其进行分包处理),那么Vue中给我们提供了一个函数:defineAsyncComponent
。
defineAsyncComponent接受两种类型的参数:
- 类型一:工厂函数,该工厂函数需要返回一个Promise对象;
- 类型二:接受一个对象类型,对异步函数进行配置;
工厂函数类型一的写法:
<script> import { defineAsyncComponent } from 'vue'; const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue")); export default { components: { AsyncHome } } </script>
对象类型类型二的写法:
<script> import { defineAsyncComponent } from "vue"; // const AsyncHome = defineAsyncComponent(() => import("./AsyncHome.vue")); import Loading from "./Loading.vue"; import Error from "./Error.vue"; const AsyncHome = defineAsyncComponent({ // 工厂函数 loader: () => import("./AsyncHome.vue"), // 加载过程中显示的组件 loadingComponent: Loading, // 加载失败时显示的组件 errorComponent: Error, // 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms) delay: 200, // 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件 // 默认值:Infinity(即永不超时,单位 ms) timeout: 3000, // 定义组件是否可挂起 | 默认值:true suspensible: false, /** * * @param {*} error 错误信息对象 * @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试 * @param {*} fail 一个函数,指示加载程序结束退出 * @param {*} attempts 允许的最大重试次数 */ onError(error, retry, fail, attempts) { if (error.message.match(/fetch/) && attempts <= 3) { // 请求发生错误时重试,最多可尝试 3 次 retry(); } else { // 注意,retry/fail 就像 promise 的 resolve/reject 一样: // 必须调用其中一个才能继续错误处理。 fail(); } }, }); export default { components: { AsyncHome, }, }; </script>
2.3. 异步组件和Suspense
注意,目前(2021-06-08)Suspense显示的是一个实验性的特性,API随时可能会修改。
Suspense是一个内置的全局组件,该组件有两个插槽:
- default:如果default可以显示,那么显示default的内容;
- fallback:如果default无法显示,那么会显示fallback插槽的内容;
<template> <div> <suspense> <template #default> <async-home></async-home> </template> <template #fallback> <loading/> </template> </suspense> </div> </template>