Vue 中预加载组件

在 Vue 中,利用 VueRouter 可以轻松的实现两个组件(页面)之间的切换,有个常用的设计就是需要在登录页登录后跳转至一个内容页,通常的做法是在登录校验完成之后立即切换路由至内容页,接着内容页发送网络请求获取渲染需要的数据然后渲染带有业务数据的 DOM:

图片

上图中,由于内容页的核心数据都是需要通过网络请求来获取,在数据获取回来之前页面处于空白(或 loading)状态,这里并没有什么逻辑问题,只是有时候可能会想,怎么将这个等待过程提前一点比如放置路由跳转之前,让内容页的初始数据准备好了再进行路由跳转?如下图示:

图片

这篇文章的主要内容将会讨论这个问题。

方法一,数据缓存

容易想到的办法是提前据通过网将内容页的数络请求获取,待数据响应后,将获取的数据缓存至内存中,接着进行页面跳转至内容页,内容页拿到缓存的初始数据进行页面渲染,从而跳过了内容页空白(或 loading)状态

一个简单的代码实现:

// contentLogic.ts

export function loadContentRecords(params: Record<string, any>)  {
  // 逻辑 A
  // 逻辑 B
  // 逻辑 C
  // ...
  return axios.post('...', params)
}
<!-- Login.vue -->

<script setup lang="ts">
import { loadContentRecords } from './contentLogic'
import router from './router'

const onSubmit = async () => {
  // 1. 登录
  await axios.post('/login', { /** ... */ })
  // 2. 登录通过后,预加载 content 的数据
  const data = await loadContentRecords({ A: false }) // ①
  // 3. 将预加载的数据放置在某一个地方
  window.data = data
  // 4. 数据加载完成并保存后,跳转至 content 页面
  router.push('/content')
}
</script>

<template>
  <button @click="onSubmit">登录</button>
</template>
<!-- Content.vue -->

<script setup lang="ts">
import { ref } from 'vue'
import { loadContentRecords } from './contentLogic'

const A = ref(false) // ②
const data = ref([])

if (window.data) {
  // 如果有数据源,直接使用
  data.value = window.data
  delete window.data
} else {
  // 否则通过接口获取
  loadContentRecords({
    A
  })
}
</script>

<template>
  <!-- ... -->
</template>

上面的实现中,可以看出此方法有一些缺陷:

  1. 为了实现能在内容页之外预先发送网络请求来获取数据,需要将内容页的数据加载逻辑(loadContentRecords  方法)抽离至公共文件中(contentLogic.ts  ),但显然这部分逻辑不应该被多余的抽离维护成单独的文件,因为它是只属于内容页的逻辑,别的组件不会使用;
  2. 内容页组件的内部其它状态需要同步维护,在上面的 Content.vue 中,有一个默认的过滤条件 A,可以看到这个过滤条件的初始值不得不维护两次,增加了维护成本和出错概率:
    • 标记  :组件外部为了保证预加载的数据正确性,需要同步组件内部的默认过滤条件;
    • 标记  :组件内部为了配合 UI 展示,定义一个 Ref 来跟视图进行绑定。
  3. 随着“下一页面“的选择可能性变多,如可能会跳转至内容页1、内容页2... 这时每个不同选择都会有第 1、2 步,变得更加难以维护。

方法二,预加载 Vue 组件

在方法一中,导致种种缺陷的原因是我们在一个功能完整的组件中,只把其中一部分的逻辑抽离出来单独执行,且这部分逻辑丢失了组件中的上下文(如过滤条件 A,或者一些分页参数等),所以不得不再维护一份意义相同的上下文来正确执行预加载操作

方法二则通过预加载组件,在不重构组件内部逻辑的前提下来实现相同的功能

在 Vue3 中,可以通过 h  方法来创建一个 VNode ,参数是一个组件对象

import { h } from 'vue'
import Content from './Content.vue'

const vnode = h(Content)

通过 render 方法将一个 VNode 渲染至 DOM 中,其中我们的目的是需要执行组件的逻辑,不需要将组件渲染进页面的 DOM 树中,因此只需要在内存中准备一个空的容器放置组件的 DOM 即可

import { h, render } from 'vue'
import Content from './Content.vue'

const vnode = h(Content)
render(vnode, window.document.createElement('div'))

至此,Content 组件已经被正常加载并挂载在内存中的一个匿名 div 中,假设 Content 的组件内部的网络请求总是需要 1s 才能完成,结合方法一中的示例,修改 login.vue:

<!-- Login.vue -->

<script setup lang="ts">
import { h, render } from 'vue'
import Content from './Content.vue'
import router from './router'

const onSubmit = async () => {
  // 1. 登录
  await axios.post('/login', { /** ... */ })
  // 2. 预加载 Content 组件
  const vnode = h(Content)
  render(vnode, window.document.createElement('div'))
  // 找个地方保存这个预加载的 VNode
  window.contentVNode = vnode
  window.setTimeout(() => {
      // 3. 1s 过后(上文中的约定时间),组件中的数据加载完成,跳转至 content 页面
      router.push('/content')
  }, 1000)
}
</script>

<template>
  <button @click="onSubmit">登录</button>
</template>

事实上,目前仅仅是内存中加载了 Content 组件并不会有缓存的效果,因为在路由跳转后,VueRouter 又会重新渲染一个全新的 Content 组件,和我们在内存中预加载的 Content 没有任何联系

借用 KeepAlive 组件的思想,在路由跳转后渲染 Content 组件时,让 Vue 知道 “这个 Content 组件有缓存,读缓存就完事了“

给预加载的 vnode 加上有缓存标识,也就是给 vnode 的 shapeFlag 属性添加已被缓存的标识

<!-- Login.vue -->

<script setup lang="ts">
import { h, render } from 'vue'
import Content from './Content.vue'
import router from './router'

const onSubmit = async () => {
  // 1. 登录
  await axios.post('/login', { /** ... */ })
  // 2. 预加载 Content 组件
  const vnode = h(Content)
  render(vnode, window.document.createElement('div'))
  // 找个地方保存这个预加载的 VNode
  window.contentVNode = vnode
  // +++++++++++++++ 这里是添加的一行,ShapeFlag 是 @vue/shared 包定义的枚举, 512 是其中的一项
  // +++++++++++++++ (源代码:COMPONENT_KEPT_ALIVE = 1 << 9)
  // +++++++++++++++ 标识这个 vnode 是有缓存的(这里实际上是借助 KeepAlive 组件的实现)
  vnode.shapeFlag |= 512
  window.setTimeout(() => {
      // 3. 1s 过后(上文中的约定时间),组件中的数据加载完成,跳转至 content 页面
      router.push('/content')
  }, 1000)
}
</script>

<template>
  <button @click="onSubmit">登录</button>
</template>

在 RouterView 组件插槽拿到了路由匹配到的组件之后,通过自定义一个“代理”组件,来判断是否有缓存的组件可以读取

<!-- App.vue -->
<script setup lang="ts">
import { getCurrentInstance, h } from 'vue'
import type { Component } from 'vue'

const MyComponent: Component = {
  props: ['is'],
  setup(props) {
    const instance = getCurrentInstance() as any

    // Vue 在对一个 VNode 进行挂载操作时,会判断此 VNode 是否有缓存(通过上面给的 "512" 标识)
    // 如有缓存,则会调用 VNode 的父元素此方法
    //(源码中这种情况父元素就是 KeepAlive,但此时借助 KeepAlive 的思想,当前这个组件也实现这个方法)
    // 如没有缓存,Vue 就会从 0 挂载一个组件
    instance.ctx.activate = (vnode: VNode, container: HTMLElement, anchor: ChildNode | null) => {
      // 只需要将缓存的 VNode 里的 DOM 结构插入到文档中即可
      container.insertBefore(vnode.component!.subTree.el! as any, anchor)
    }

    // setup 可返回一个函数,表示此组件的 render 函数
    return () => {
      const { is } = props
      if (!is) return null

      // 找到预先加载的 VNode 了,返回这个内存中的 VNode,且这个 VNode 的 shapeFlag 是带有 “512” 标识的
      // 进入 Vue 后续的挂载逻辑后,就会走上面的 `activate` 方法
      if (window.contentVNode) return window.contentVNode
      // 不是缓存的 VNode,原样返回即可
      else return is
    }
  },
}
</script>

<template>
  <RouterView v-slot="{ Component }">
    <MyComponent :is="Component"></MyComponent>
  </RouterView>
</template>

至此实现完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/472726.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

极简生活|2024年让自己越来越好的18个极简好习惯

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 转眼间已经进入了2024年&#xff0c;新的一年&#xff0c;新的开始。 俗话说&#xff1a;百尺高台起于垒土&#xff0c;千里之堤毁于蚁穴。 好习惯积累的越多&#xff0c;坏习惯越来越少&#xff0c;我们的生活才能越…

Iterator对象功能学习

package config;import java.util.Iterator; import java.util.Properties; import java.util.Set;/*** 这个类演示了如何使用Properties类来存储和访问键值对。* Properties类继承自Hashtable&#xff0c;因此它可以用来存储键值对数据&#xff0c;且支持同步。*/ public clas…

MySQL介绍

一、MySQL数据库介绍 1、发展史 1996年 MySQL1.0 2008年1月16日 Sun公司收购了 MySQL 2009年4月20日 Oracle收购了Sun公司 MySQL是一种开放源代码的关系型数据库管理系统 使用最常用的数据库管理语言 SQL&#xff08;结构化查询语言&#xff09; MySQL是开放源代码的 因此所有…

基于Springboot的员工健康管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的员工健康管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

【c++】c++基本语法知识-命名空间-输入输出-缺省参数

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;c_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.命名空间 1.2 命名空间定义 1.3 命名空间使用 命名空间的三种使用方式 2.C输入&输出 std命名空间的使用惯例 3.缺省参数 3…

Win11初始化系统遇一文解决

这个是目录 一、设置内的初始化无法使用时&#xff0c;使用以下工具二、将桌面移动到D盘三、解决win11桌面右键创建只有一个带盾牌的文件夹问题四、win11 系统停止更新五、office安装1、使用的是 Office Tool plus2、使用WPS 六、D盘有感叹号七、打开组策略编辑器(gpedit.msc)失…

【Docker】-- 如何安装docker

一、安装docker 首先要安装一个yum工具 yum install -y yum-utils 安装成功后&#xff0c;执行命令&#xff0c;配置Docker的yum源&#xff1a; yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 最后&#xff0c;执行命令&#x…

Windows 安装 Graalvm 环境(JDK 22)

Windows 安装 Graalvm 环境 系统&#xff1a;Windows 11 官方参考指南&#xff1a;https://www.graalvm.org/jdk22/docs/getting-started/windows/ 安装 Graalvm & 下载 native-image 下载 Graalvm 并安装 下载的 SDK 选择最新版&#xff08;JDK 22&#xff09;&#xff…

UE4_官方动画内容示例1.3_ 运动混合空间(Locomotion BlendSpace)

如何使用运动&#xff08;Locomotion&#xff09;混合空间将Actor在不同方向上及不同速度的运动混合起来。&#xff08;例如&#xff0c;展示了一个混合了以不同速度向后、前、左和右走路/跑步动作的Actor&#xff09;。 一、相关知识点&#xff1a; 混合空间是允许根据多个输…

《汽车数据安全若干问题合规实践指南》正式发布(百度盘下载)

指南针对汽车数据安全的重要合规内容&#xff0c;结合汽车行业特有场景&#xff0c;参考行业最佳实践&#xff0c;提出合规实践建议。指南旨在进一步提高汽车行业数据安全保护水平&#xff0c;增强汽车企业数据安全合规保障能力&#xff0c;推动汽车数据价值安全使用&#xff0…

初始Java篇(JavaSE基础语法)(2)

个人主页&#xff08;找往期文章包括但不限于本期文章中不懂的知识点&#xff09;&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 目录 逻辑控制 顺序结构 分支结构 if语句 switch 语句 循环结构 while 循环 for 循环 do while 循环 输入输出 输出到控制台 从键盘输入 …

AIGC——ComfyUI工作流搭建、导入与常用工作流下载

工作流 ComfyUI工作流是一个基于图形节点编辑器的工作流程&#xff0c;通过拖拽各种节点到画布上&#xff0c;连接节点之间的关系&#xff0c;构建从加载模型到生成图像的流程。每个节点代表一个与Stable Diffusion相关的模型或功能&#xff0c;节点之间通过连线传递图片信息。…

蓝桥杯-数的潜能-求快速幂

题目 思路 --将数字拆分成加和的形式&#xff0c;并且相乘。数据范围到10的18次方&#xff0c;暴力肯定不行&#xff0c;要找规律。拆分成1肯定不行&#xff0c;对乘法没有贡献&#xff0c;2可以&#xff0c;3也可以&#xff0c;4、5、6等大于3的数字都可以用2和3来表示。所以…

【堆】Top-K问题

标题&#xff1a;C语言库函数scanf&#xff08;&#xff09;解读 水墨不写bug &#xff08;图片来源于网络&#xff09; 正文开始&#xff1a; Top-K问题是一类问题的统称&#xff1a; 即根据对象的某一属性&#xff0c;找出这个属性最突出的K个对象&#xff0c;并且通常对象…

简单了解多线程

并发和并行 并发&#xff1a; 在同一时刻&#xff0c;多个指令在单一CPU上交替指向 并行&#xff1a;在同一时刻&#xff0c;多个指令在多个CPU上同时执行 2核4线程&#xff0c;4核8线程&#xff0c;8核16线程&#xff0c;16核32线程 基础实现线程的方式 Thread :继承类 &…

OpenJDK11的安装及配置

OpenJDK11的安装及配置: 下载链接&#xff1a;http://jdk.java.net/archive/ 1. 下载 点击链接下载OpenJDK11的zip压缩文件** 选择Windows版本 解压 解压成功。 2. 环境配置 打开设置----选择相关设置中的高级系统设置 选择高级—环境变量 系统变量 下添加JAVA_HOME …

开发技术-FeignClient 对单个接口设置超时时间

1. 背景 FeignClient 调用某个接口&#xff0c;3s 没有结果就需要停止&#xff0c;处理后续业务。 2. 方法 FeignClient 自定义 name 属性 FeignClient(name "aaa" , url "xxx") public interface TestApi {ResponseBodyPOSTMapping(value "xx…

STM32编程控制电机实现PID速度闭环中的堵转检测

实现PID速度闭环控制是编码器电机驱动中的重要任务&#xff0c;而堵转检测和控制则是保证电机正常运行的关键环节。在本文中&#xff0c;我们将详细探讨STM32编程驱动编码器电机实现PID速度闭环控制中堵转检测和控制的方法。 一、堵转检测方法 编码器反馈&#xff1a; 编码器…

软考 系统架构设计师系列知识点之系统性能(1)

所属章节&#xff1a; 第2章. 计算机系统基础知识 第9节. 系统性能 系统性能是一个系统提供给用户的所有性能指标的集合。它既包括硬件性能&#xff08;如处理器主频、存储器容量、通信带宽等&#xff09;和软件性能&#xff08;如上下文切换、延迟、执行时间等&#xff09;&a…