异步页面或异步组件加载

373 阅读8分钟

在项目涉及到用户体验和性能优化,不得不提异步组件和异步页面。这对于创建高性能、响应式且用户友好的应用程序至关重要。通过延迟加载、代码拆分和按需加载,它们可以提高性能、优化用户体验并提高代码的可维护性。 最终示例效果如下

案例.gif

异步页面或异步组件优化作用和意义以下几个方面:

  1. 提高性能:通过延迟加载或分块加载代码,异步组件和异步页面可以显著提高应用程序的性能。这对于大型和复杂的应用程序尤为重要,因为它们通常需要加载大量代码。通过异步加载,可以减少初始加载时间,从而为用户提供更好的体验。
  2. 优化用户体验:异步加载还可以优化用户体验。当页面或组件延迟加载时,用户可以立即看到加载指示符或占位符,从而让他们知道正在加载内容。这比长时间等待所有内容加载要好得多,因为用户可以继续与应用程序交互,而不用等待。
  3. 代码拆分:异步组件和异步页面促进代码拆分,这有助于提高可维护性和可扩展性。通过将代码拆分成较小的模块,可以更容易地管理和更新代码,特别是对于大型应用程序。此外,代码拆分可以减少捆绑文件的大小,从而加快加载时间。
  4. 按需加载:异步组件和异步页面允许按需加载内容。这意味着只有在需要时才会加载特定组件或页面。这可以节省带宽并减少内存使用,特别是在处理大量数据或具有复杂导航的应用程序时。
  5. 渐进式显示:异步页面还可以实现渐进式显示。这允许页面内容随着加载的进行逐渐显示,为用户提供更好的体验。例如,一个异步页面可以先显示标题和侧边栏,然后随着内容的加载逐渐显示正文和图像。

通过vite创建vue3项目

npm create vite@latest


Ok to proceed? (y) y
√ Project name: ... vite-vue3-app
√ Select a framework: » VueSelect a variant: » JavaScript

image.png

创建完成后进入该项目下,初始化项目并运行

  cd vite-vue3-app
  npm install
  npm run dev

image.png

image.png

创建tabbar

由于我这边demo是移动端模板,需要导入一个移动端ui库(主要用切换页面和骨架屏ui,这个看各位熟悉度,antd、elementui都可以)

以我当前项目为例子,vntd4框架vant-ui.github.io/vant/#/zh-C…

# Vue 3 项目,安装最新版 Vant 
npm i vant 
# Vue 2 项目,安装 Vant 2 
npm i vant@latest-v2
# 通过 yarn 安装 
yarn add vant

安装完成进入main.js完成组件的导入,完整的文件如下

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//导入Tabbar
import { Tabbar, TabbarItem } from 'vant';
//引入vant组件样式
import 'vant/lib/index.css';
//导入路由,后面会讲
import router from './router'
//导入骨架屏
import {
    Skeleton,
    SkeletonTitle,
    SkeletonImage,
    SkeletonAvatar,
    SkeletonParagraph,
  } from 'vant';


//添加插件
createApp(App)
.use(router)
.use(Tabbar)
.use(TabbarItem)
.use(Skeleton)
.use(SkeletonTitle)
.use(SkeletonImage)
.use(SkeletonAvatar)
.use(SkeletonParagraph)
.mount('#app')

然后修改App.vue页面,完整代码如下:

<script setup>
import HelloWorldVue from "./components/HelloWorld.vue";
  import { ref } from "vue";
  const active = ref(0);
</script>
<template>

<div>
  
  <router-view />
  <van-tabbar v-model="active">
    <van-tabbar-item icon="home-o" > <router-link to="/">主页</router-link></van-tabbar-item>
    <van-tabbar-item icon="setting" ><router-link to="/Setting">设置</router-link></van-tabbar-item>
    
  </van-tabbar>
</div>
</template>

<style scoped>
#app {
  margin: 0 auto !important;
  padding: 0 !important;
  text-align: center;
}
</style>

然后完成下面tabber功能

image.png

创建页面

src文件夹下创建两个文件Home.vueSetting.vue

image.png

Setting.vue页面上填写下面内容,Home.vue页面先暂时和Setting.vue页面保持一致

<template>
    <div>切换页面测试</div>
</template>

安装路由

运行下面代码

npm install vue-router

配置路由,在src文件夹下创建两个文件index.jsroutes.js

image.png

index.js文件内容如下

import { createRouter,createWebHistory } from "vue-router";
import routes from "./routes"
export default createRouter({
    history:createWebHistory(),//路由方式history
    routes
})

routes.js文件内容如下

import { getAsyncPage } from "../utils/index.js"
const Home = getAsyncPage("../views/Home.vue");
const Setting = getAsyncPage("../views/Setting.vue");

export default [
    {path:"/",component:Home},
    {path:"/Setting",component:Setting},
]

记得在main.js导入插件 image.png

路由安装完成,此时运行代码,可以看到页面如下,并且可以切换页面

image.png

完善其他页面

先看下页面结构,其他页面主要包含三个,错误页,骨架屏页,页面切换加载中页面,下面将展示完整的代码。 image.png

Error.vue页面,简单展示错误消息,并且错误消息通过插槽slot传入。

<template>
    <div style="color: red;font-size: 18px;" >
      <slot></slot>
    </div>
  </template>

Skeleton.vue页面,展示加载中效果,并且该页面占满整个父级组件

注意:这里是举例子:可以根据实际业务需求进行调整,例如放图片,或者svg动画。

<template>
    <div class="temp" style="">
        <van-skeleton title :row="1" />
    </div>
</template>
<style scoped>
.temp{
    width: 100%; 
    height: 100%;
    margin: auto 0;
    padding: 20px 0;
}
</style>

Loading.vue

<template>
    <svg viewBox="25 25 50 50" class="circular">
      <circle cx="50" cy="50" r="20" fill="none" class="path"></circle>
    </svg>
  </template>
  <style scoped>
    .circular {
      height: 42px;
      width: 42px;
      animation: loading-rotate 2s linear infinite;
    }

    .path {
      animation: loading-dash 1.5s ease-in-out infinite;
      stroke-dasharray: 90, 150;
      stroke-dashoffset: 0;
      stroke-width: 2;
      stroke: #409eff;
      stroke-linecap: round;
    }
    @keyframes loading-rotate {
      100% {
        transform: rotate(1turn);
      }
    }
    @keyframes loading-dash {
      0% {
        stroke-dasharray: 1, 200;
        stroke-dashoffset: 0;
      }
      50% {
        stroke-dasharray: 90, 150;
        stroke-dashoffset: -40px;
      }
      100% {
        stroke-dasharray: 90, 150;
        stroke-dashoffset: -120px;
      }
    }
  </style>

完善Home页面

home页面首先梳理模块,如下图Home.vue有两个子组件,一个Block2.vueBlock3.vue,在Block3.vue这个页面下有个子列表组件Bitem.vue组件。 image.png

当我们了解关系后,下面列举完整代码

Block2.vue页面代码:

<template>
    我是异步组件
</template>

Block3.vue

<script setup>
import { getDefineAsyncComponent } from "../utils/index.js";
const Bitem = getDefineAsyncComponent("../components/Bitem.vue");
</script>
<template>
<div v-for="i in 5" :key="i" style="width: 80vw;">
    <Bitem class="item-c">
        <template #default>
            <div>第{{i}}个item</div>
        </template>
    </Bitem>
</div>
</template>
<style>
.item-c{
    width: 100%;  
    padding: 10px 0;
    border: 1px solid #000;
    margin-bottom: 5px;
}
</style>

Bitem.vue

<template>
  <div >
    <slot></slot>
  </div>
</template>

Home.vue

<script setup>
import { getDefineAsyncComponent } from "../utils/index.js";
const Block2 = getDefineAsyncComponent("../components/Block2.vue");
import Block3 from '../components/Block3.vue'
</script>

<template>
  <div>
    <div class="flex-content">
      <div class=" left">
        <h6>组件1</h6>
      </div>
      <div class=" right">
        <Block2 />
      </div>
    </div>
    <div class="block ">
      <h6>下面为异步组件</h6>
    </div>
    <div class="block big">
      <Block3 />
    </div>
  </div>
</template>

<style scoped>
.block {
  margin: 15px;
  height: 10vh;
  width: 80vw;
  border: 1px solid #ebebeb;
  border-radius: 3px;
  box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6),
    0 2px 4px 0 rgba(232, 237, 250, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.flex-content {
  margin: 15px;
  height: 10vh;
  width: 80vw;
  display: flex;
  align-content: center;
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: center;

}
.left {
  width: 30%;
  height: 100%;
  border: 1px solid #ebebeb;
  border-radius: 3px;
  box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6),
    0 2px 4px 0 rgba(232, 237, 250, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}
.right {
  width: 60%;
  height: 100%;
  border: 1px solid #ebebeb;
  border-radius: 3px;
  box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6),
    0 2px 4px 0 rgba(232, 237, 250, 0.5);

  display: flex;
  justify-content: center;
  align-items: center;
}
.big {
  height: 50vh;
  justify-content: flex-start
}
</style>

在上面的代码中导入工具类,getDefineAsyncComponent,下面讲解工具类的时候会详细将,自此所以页面代码写完了。

工具类

在讲工具类的时候先导入一个插件nprogress,Nprogress 是一个 JavaScript 进度条库,用于显示页面加载和 AJAX 请求的进度。它提供了一个简单的方法来在应用中添加视觉进度指示器。 Nprogress 插件的特点包括:**轻量级:**仅 2KB 大小,不会对页面性能造成显著影响, **易于使用:**通过几个简单的方法即可轻松集成到应用程序中。

npm install nprogress

安装完成后线上完整的代码utils/index.js的代码,再进行讲解

import { defineAsyncComponent, h, } from 'vue';
import Lodaing from "../components/Loading.vue";
import Skeleton from "../components/Skeleton.vue";
import Error from "../components/Error.vue";
import 'nprogress/nprogress.css';
import nProgress from 'nprogress';

nProgress.configure({ 
    trickleSpeed: 50,
    showSpinner: false,
 });
 
export function getDefineAsyncComponent(path){
    return defineAsyncComponent(
        {
            loader: async () => {
                await delay();
                if (Math.random() < 0.5) {
                    throw new TypeError("加载失败");
                }
                return import(path);
            },
            loadingComponent: Skeleton,
            errorComponent: {
                render() {
                    return h(Error, "组件加载失败")
                }
            },
        }
    )
}

export function getAsyncPage(path){
    return defineAsyncComponent(
        {
            loader: async () => {
               nProgress.start();
               await delay();
               nProgress.done();
                return import(path)
            },
            loadingComponent: Lodaing,
        }
    )
}

export function delay(duration) {
    if (!duration) {
        duration = random(1000, 5000);
    }
    return new Promise((resolove) => {
        setTimeout(() => {
            resolove(duration)
        }, duration);
    });
}

export function random(min, max) {
    return Math.floor(Math.random() * (max - min)) + min
}

1、引入的依赖

  • Vue相关: 使用defineAsyncComponentvue库中定义异步组件,h函数用于在渲染函数中创建VNode(虚拟节点)。
  • 自定义组件: 引入了LoadingSkeletonError三个Vue组件,分别用于展示加载中、骨架屏和错误提示的UI。
  • nprogress: 使用轻量级的进度条库,用于在顶部显示加载进度,通过nprogress.cssnprogress库引入。

2、配置nprogress

  • 使用nProgress.configure配置了nprogress的行为,设置trickleSpeed(进度条增加的速率)和showSpinner(是否显示加载动画)。

3、定义的函数

getDefineAsyncComponent(path)

  • 此函数返回一个通过defineAsyncComponent定义的异步组件。
  • 异步加载时,首先调用delay函数实现网络延迟加载效果。
  • 延迟结束后,模拟网络异常或请求接口失败,通过Math.random()模拟加载成功或失败的情况。失败时抛出TypeError
  • 加载过程中使用Skeleton作为加载组件,加载失败时显示自定义的Error组件,并传递了错误信息("组件加载失败")。

getAsyncPage(path)

  • 类似于getDefineAsyncComponent,但专门用于页面级组件的异步加载。
  • 在加载前调用nProgress.start()开始显示进度条,加载完成后调用nProgress.done()隐藏进度条。
  • 加载过程中使用Lodaing(应为Loading)作为加载组件。

delay(duration)

  • 一个辅助函数,用于模拟实现网络延迟效果,。
  • 如果没有指定duration,则随机生成一个1000到5000毫秒之间的延迟时间。
  • 使用setTimeout实现延迟,但注意这里resoloveresolve的拼写错误。

random(min, max)

  • 生成一个介于minmax之间的随机整数。

以上解释Vue应用中实现动态加载组件时的加载效果、错误处理和延迟加载功能,整个demo完成。