Suspense & 异步组件(骨架屏懒加载实现)

615 阅读2分钟

suspense.gif

本文章demo来源: 小满哥Vue教程

<Suspense> 是一项实验性功能。它不一定会最终成为稳定功能,并且在稳定之前相关 API 也可能会发生变化。

使用方式

<Suspense> 将在内存中渲染其默认的插槽内容(#default)。如果在这个过程中遇到任何异步依赖,则会进入挂起状态。在挂起状态期间,展示的是后备内容(#fallback)。当所有遇到的异步依赖都完成后,<Suspense> 会进入完成状态,并将展示出默认插槽的内容。

事件

  • pending:事件是在进入挂起状态时触发。
  • resolve: 事件是在 default 插槽完成获取新内容时触发。
  • fallback:事件则是在 fallback 插槽的内容显示时触发。

父组件

<!-- 父组件 -->
<template>
  <div>
    <Suspense>
      <template #default>
        <AsyncVue />
      </template>
      <template #fallback>
        <Skeleton />
      </template>
    </Suspense>
  </div>
</template><script setup lang="ts">
import { defineAsyncComponent } from 'vue';
import Skeleton from '@/components/skeleton.vue'const AsyncVue = defineAsyncComponent(() => import('@/components/sync.vue'))
</script><style scoped></style>

挂起状态:骨架屏站位组件(skeleton)

<!-- skeleton.vue-->
<template>
    <div>
        <div class="sync">
            <div class="sync-content">
                <div class="img"></div>
                <div class="sync-pop">
                    <div></div>
                    <div>&nbsp;</div>
                </div>
            </div>
            <hr>
            <div class="content"></div>
            <div class="content"></div>
        </div>
    </div>
</template>
<style lang="less" scoped>
.sync {
    // background-color: #d4d9e1;
    padding: 30px;
}
​
.sync-content {
    display: flex;
    align-items: center;
​
    .img {
        width: 100px;
        height: 100px;
        background-color: #d4d9e1;
        border-radius: 50%;
        margin-right: 20px;
        // object-fit: cover;
    }
    .sync-pop{
        width: 20%;
        height: 20px;
        background-color: #d4d9e1;
    }
}
.content{
        width: 100%;
        height: 20px;
        background-color: #d4d9e1;
        margin: 20px 0;
    }
</style>

完成状态:异步组件(sync)

<!-- sync -->
<template>
  <div>
    <div class="sync">
      <div class="sync-content">
        <div><img :src="data.url"></div>
        <div class="sync-pop">
          <div>{{ data.name }}</div>
          <div>&nbsp;</div>
        </div>
      </div>
      <hr>
      <div>{{ data.desc }}</div>
    </div>
  </div>
</template><script setup lang="ts">
import { axios } from '@/server/axios';
​
interface Data {
  data: {
    url: string,
    name: string,
    age: number,
    desc: string
  }
}
​
const { data } = await axios.get<Data>('./data.json')
</script><style lang="less" scoped>.sync{
  background-color: #d4d9e1;
  padding: 30px;
}
.sync-content {
  display: flex;
  align-items: center;
​
  img {
    width: 100px;
    height: 100px;
    // background-color: grey;
    border-radius: 50%;
    margin-right: 10px;
    object-fit: cover;
  }
}
</style>

自己封装的小axios

export const axios = {
    get <T>(url: string): Promise<T> {
        return new Promise((resolve) => {
            const xhr = new XMLHttpRequest()
            xhr.open('GET', url)
            xhr.onreadystatechange = () => {
                if(xhr.status === 200 && xhr.readyState === 4) {
                    setTimeout(() => {
                        resolve(JSON.parse(xhr.responseText))
                    }, 2000)
                }
            }
            xhr.send(null)
        })
    }
}

json数据(放在public/目录下资源不会被打包,可以直接访问)

{
    "data": {
        "name": "James Harden",
        "age": 24,
        "url": "https://n.sinaimg.cn/sinakd20230413ac/300/w1166h734/20230413/2596-5999a0afc974e76bdc43c261abf62716.jpg",
        "desc": "詹姆斯·哈登(James Harden),1989年8月26日出生于美国加利福尼亚州洛杉矶(Los Angeles, California),美国职业篮球运动员,司职后卫,效力于NBA费城76人队"
    }
}

附带知识(以下是附带知识,顺便记录)

defineSyncComponent

import { defineAsyncComponent } from 'vue';
​
//引入方式1
const Sync = defineAsyncComponent(() => import('@/components/suspense/sync.vue'))
//引入方式2
const Sync = defineAsyncComponent(() => {
      loader: () => import('@/components/suspense/sync.vue'),
      // 加载异步组件时使用的组件
      loadingComponent?: Component,
      // 展示加载组件前的延迟时间,默认为 200ms
      delay: 200,
      // 加载失败后展示的组件
      errorComponent: ErrorComponent,
      // 如果提供了一个 timeout 时间限制,并超时了
      // 也会显示这里配置的报错组件,默认值是:Infinity
      timeout: 3000
})

异步组件可以拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。vue会将异步组件分开打包。

异步组件打包.png

安装less 、设置路径别名(alias)

npm install less -S -D

vite.config.js配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
​
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: [
      {
        find: "@",
        replacement: resolve(__dirname, 'src')
      }
    ]
  },
  css: {
    preprocessorOptions: {
      less: {
        math: "always", // 括号内才使用数学计算
        globalVars: {
          // 全局变量
          mainColor: "red",
        },
      },
    },
  },
})

tsconfig.json添加配置

"baseUrl": ".",
"paths": {
  "@/*": [ "src/*" ],
}

ts路径识别问题

微信截图_20230419210132.png 在tsconfig.json添加配置

 "forceConsistentCasingInFileNames": false,