当前位置: 首页 > news >正文

一次渲染十万条数据:前端技术优化(上)

今天看了一篇文章,写的是一次性渲染十万条数据的方法,本文内容是对这篇文章的学习总结,以及知识点补充。

在现代Web应用中,前端经常需要处理大量的数据展示,例如用户评论、商品列表等。直接渲染大量数据会导致浏览器性能问题,如卡顿和延迟。本文将探讨几种优化策略,帮助开发者提高网页性能,优化用户体验。

方法一:通过document直接渲染十万条数据

示例代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法一:一次性渲染十万条数据for (let i = 0; i < 100000; i++) {const li = document.createElement('li')li.innerText = `我是第${i + 1}条数据`app.appendChild(li)}console.log('js运行耗时', Date.now() - now)setTimeout(() => {console.log('dom渲染耗时', Date.now() - now)})</script>
</html>

结果

请添加图片描述

问题

当页面需要一次性渲染大量数据时,直接将所有数据渲染到DOM中会迅速消耗浏览器资源,造成性能瓶颈。这种方法虽然简单,但会导致浏览器响应缓慢,用户体验差。

补充知识1: JS事件循环之宏任务和微任务

在 JavaScript 中,事件循环是处理并发的机制,它允许执行非阻塞的操作。事件循环的核心概念包括宏任务(macro tasks)和微任务(micro tasks)。它们的执行顺序和机制十分重要,理解它们有助于更好地编写异步代码。

宏任务与微任务
  1. 宏任务 (Macro Task):

    • 宏任务是较大粒度的任务,它通常由以下几部分组成:
      • 整个脚本(执行的上下文)
      • setTimeout
      • setInterval
      • I/O 操作(如网络请求)
    • 宏任务的执行是按照创建顺序依次执行的。
  2. 微任务 (Micro Task):

    • 微任务是相对较小粒度的任务,通常用于处理短小的异步操作,主要由以下几部分组成:
      • Promise.then().catch()
      • MutationObserver
    • 微任务在当前宏任务执行完毕后立即执行,且在浏览器进行下一次重绘之前完成所有微任务。这意味着微任务的优先级高于宏任务。
执行顺序
  1. 首先,事件循环从宏任务队列中取出一个宏任务并执行。
  2. 执行完宏任务后,查看微任务队列,执行所有微任务,直到微任务队列为空。
  3. 一旦微任务队列为空,浏览器会进行渲染(重绘),然后再从宏任务队列中取出下一个宏任务。
  4. 重复这个过程,直到所有任务都完成。
示例

下面是一个简单的示例,帮助理解宏任务和微任务的执行顺序:

console.log('Start');setTimeout(() => {console.log('Timeout 1');
}, 0);new Promise((resolve, reject) => {console.log('Promise 1');resolve('Promise 1 resolved');
}).then((res) => {console.log(res);
});process.nextTick(() => {console.log('Next Tick');
});setTimeout(() => {console.log('Timeout 2');
}, 0);new Promise((resolve, reject) => {console.log('Promise 2');resolve('Promise 2 resolved');
}).then((res) => {console.log(res);
});console.log('End');
执行输出

执行上面的代码将会输出以下内容:

Start
Promise 1
Promise 2
End
Promise 1 resolved
Promise 2 resolved
Next Tick
Timeout 1
Timeout 2
  • 在 JavaScript 的事件循环中,宏任务的执行优先于微任务,但在宏任务完成后,微任务会立即执行,确保在下一个宏任务开始之前执行所有微任务。
  • 理解这一流程能够帮助开发者更好地预测和管理异步代码的执行顺序及其结果。

补充知识2: 执行渲染操作,更新界面为什么发生在执行 setTimeout之前?

在 JavaScript 中,事件循环和异步操作的处理方式是理解 setTimeout 和界面更新之间关系的关键。

界面更新的时机

在 JavaScript 中,界面更新通常在以下几个时刻发生:

  • 当主线程空闲时(即没有其他任务在执行时),浏览器会进行界面更新。
  • 在一个宏任务(如 setTimeout)执行之后,浏览器会检查微任务队列并执行所有的微任务,然后再进行界面更新。
为什么界面更新发生在执行 setTimeout 之前
  1. 执行顺序:当你调用 setTimeout 时,传入的回调函数不会立即执行,而是被放入宏任务队列中。主线程会继续执行当前的任务。
  2. 界面更新:在当前任务结束后,浏览器会检查是否有需要更新的界面。此时,如果有 DOM 的改变(例如通过某个函数修改了 DOM),浏览器会更新界面。
  3. 执行 setTimeout 的回调:在主线程空闲并完成微任务后,浏览器会从宏任务队列中取出 setTimeout 的回调并执行。
示例
console.log("Start");
setTimeout(() => {console.log("Inside setTimeout");
}, 0);
console.log("End");

输出顺序将是:

Start
End
Inside setTimeout

在 JavaScript 中,界面更新发生在当前任务完成后和 setTimeout 回调执行之前。理解这一点对于优化性能和确保用户界面流畅性非常重要。

补充知识3: 回流与重绘

页面的回流与重绘是指浏览器在渲染网页时的两种重要过程。

  1. 回流(Reflow):当页面的结构或内容发生变化时(比如增加、删除或修改元素的大小、位置等),浏览器需要重新计算元素的几何属性,以确定它们的位置和大小。这一过程称为回流。回流会影响整个文档的布局,因此较为耗性能。
  2. 重绘(Repaint):当元素的外观发生变化(比如背景色、字体颜色等)但并不影响布局时,浏览器只需重新绘制该元素,而无需重新计算其几何属性。这一过程称为重绘。重绘通常比回流消耗的性能要少。
    总结来说,回流是布局计算,重绘是外观更新。在优化网页性能时,应尽量减少这两个过程的发生。

方法二:分批渲染

为了解决直接渲染带来的性能问题,我们可以采用分批渲染的方法。通过将数据分成小块,逐一渲染,可以减轻浏览器的即时负担。

使用 setTimeout 进行分批渲染

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法二:分批渲染十万条数据const total = 100000const loadOnce = 20const page = total / loadOnceconst index = 0function renderData(curTotal, curIndex) {let pageCount=Math.min(loadOnce,curTotal)setTimeout(() => {for (let i = 0; i < pageCount; i++) {const li = document.createElement('li')li.innerText = `我是第${i}条数据`app.appendChild(li)}renderData(curTotal-pageCount, curIndex + pageCount)})}renderData(total, index)</script>
</html>

结果

请添加图片描述

问题

当用户往下翻的时候有可能那一瞬间看不到东西

方法三、使用requestAnimationFrame替代setTimeout

requestAnimationFrame 是一种更高效的分批渲染方法,它允许在浏览器的绘制周期中执行动画和渲染,从而提高性能。

示例代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法三:使用requestAnimationFrame渲染十万条数据const total = 100000const loadOnce = 20const page = total / loadOnceconst index = 0function renderData(curTotal, curIndex) {let pageCount=Math.min(loadOnce,curTotal)requestAnimationFrame(() => {for (let i = 0; i < pageCount; i++) {const li = document.createElement('li')li.innerText = `我是第${i}条数据`app.appendChild(li)}renderData(curTotal-pageCount, curIndex + pageCount)})}renderData(total, index)</script>
</html>

补充知识4: 什么是requestAnimationFrame?

requestAnimationFrame 是一个用于优化网页动画效果的 JavaScript 方法。它指示浏览器在下次重绘之前调用指定的回调函数,从而实现基于帧的动画效果。使用 requestAnimationFrame 的一个主要优点是它能够根据浏览器的绘制频率来调整动画的更新速率,从而使动画更加流畅和高效。

使用场景:
  1. 平滑动画:如果你在进行平移动画、旋转、缩放等效果时,使用 requestAnimationFrame 可以确保动画的每一帧都在浏览器的绘制周期内更新,这有助于避免由于使用 setTimeoutsetInterval 而导致的帧率不稳定。
  2. 减少 CPU 消耗requestAnimationFrame 还具有智能调节的功能,特别是在用户切换标签页或者浏览器窗口不在视野内时,它会自动停止调用回调函数,从而节省资源。

基本用法:

function animate() {// 更新动画状态// ...// 请求下一帧requestAnimationFrame(animate);
}
// 开始动画
requestAnimationFrame(animate);

在上面的代码中,animate 函数会执行动画的逻辑,并通过 requestAnimationFrame(animate) 请求下一帧的更新。这样,animate 函数会在浏览器准备好下一个绘制周期时被调用。

优势:

  • 帧率同步requestAnimationFrame 会使动画的帧率与浏览器的刷新率(通常是60帧每秒)同步,从而提高流畅性。

  • 性能优化:当页面不在视野中时,requestAnimationFrame 会暂停动画的执行,从而减少 CPU 和 GPU 的使用。

  • 简洁易用:API 简单直观,更容易使用来控制动画的生命周期。

总之,使用 requestAnimationFrame 是在现代网页应用中实现高性能动画的推荐方式。

方法四:利用 DocumentFragment

DocumentFragment 是一个轻量级的文档对象,可以用于在内存中组装一组节点,然后一次性添加到DOM中,减少DOM操作次数。

示例代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>一次性渲染十万条数据</title>
</head>
<body><ul id="app"></ul>
</body>
<script>const app = document.querySelector('#app')let now = Date.now()// 方法四:使用文档碎片渲染十万条数据const total = 100000const loadOnce = 20const page = total / loadOnceconst index = 0function renderData(curTotal, curIndex) {let documentFragment = document.createDocumentFragment()let pageCount=Math.min(loadOnce,curTotal)requestAnimationFrame(() => {for (let i = 0; i < pageCount; i++) {const li = document.createElement('li')li.innerText = `我是第${i}条数据`documentFragment.appendChild(li)}app.appendChild(documentFragment)renderData(curTotal-pageCount, curIndex + pageCount)})}renderData(total, index)</script>
</html>

总结

在处理大量数据渲染时,选择合适的方法至关重要。直接渲染虽然简单,但性能较差。分批渲染、requestAnimationFrameDocumentFragment 提供了更优的性能解决方案。开发者应根据具体情况选择最合适的方法,以确保应用的流畅性和用户体验。

补充知识5:什么是DocumentFragment?

DocumentFragment 是一个轻量级的文档对象,表示一个可以包含多个节点的虚拟容器。它不是文档中的实际部分,而是用来在内存中组装一组节点,然后一次性地将它们添加到文档中。这种做法可以提高性能,因为它减少了对 DOM 的多次操作,降低了重绘和重排的次数。

作用和优势:
  1. 性能优化:直接多次操作 DOM 会导致浏览器频繁地重绘和重排,从而降低性能。使用 DocumentFragment 可以将多个 DOM 操作合并为一个,从而显著提高性能。

  2. 内存管理DocumentFragment 只存在于内存中,直到你将它的内容添加到实际的 DOM 中。这使得在构建大型 DOM 结构时更节省内存。

  3. 灵活性:你可以使用 DocumentFragment 来临时组装和修改多个节点,然后一次性插入到 DOM 中,保持 DOM 的一致性。

基本用法:

下面是一个简单的例子,展示如何使用 DocumentFragment 来添加多个节点到 DOM 中:


// 创建一个 DocumentFragmentconst fragment = document.createDocumentFragment();// 创建几个新的元素const li1 = document.createElement('li');li1.textContent = 'Item 1';const li2 = document.createElement('li');li2.textContent = 'Item 2';const li3 = document.createElement('li');li3.textContent = 'Item 3';// 将元素添加到 DocumentFragment 中fragment.appendChild(li1);fragment.appendChild(li2);fragment.appendChild(li3);// 将 DocumentFragment 追加到现有的 DOM 中document.getElementById('myList').appendChild(fragment);

在上面的示例中,使用 DocumentFragment 创建并组合了多个 li 元素,最后一次性将它们添加到一个列表中。这样可以避免在将每个 li 添加到 DOM 时引起的多次重排和重绘。

DocumentFragment 是一个非常有用的工具,适用于需要频繁与 DOM 交互时,帮助保持性能和优化操作。在需要插入或修改多个节点时,使用 DocumentFragment 是个不错的选择。

参考文章:
https://juejin.cn/post/7407763018471948325

相关文章:

一次渲染十万条数据:前端技术优化(上)

今天看了一篇文章&#xff0c;写的是一次性渲染十万条数据的方法&#xff0c;本文内容是对这篇文章的学习总结&#xff0c;以及知识点补充。 在现代Web应用中&#xff0c;前端经常需要处理大量的数据展示&#xff0c;例如用户评论、商品列表等。直接渲染大量数据会导致浏览器性…...

springboot实训学习笔记(5)(用户登录接口的主逻辑)

接着上篇博客学习。上篇博客是已经基本完成用户模块的注册接口的开发以及注册时的参数合法性校验。具体往回看了解的链接如下。 springboot实训学习笔记&#xff08;4&#xff09;(Spring Validation参数校验框架、全局异常处理器)-CSDN博客文章浏览阅读576次&#xff0c;点赞7…...

python中网络爬虫框架

Python 中有许多强大的网络爬虫框架&#xff0c;它们帮助开发者轻松地抓取和处理网页数据。最常用的 Python 网络爬虫框架有以下几个&#xff1a; 1. Scrapy Scrapy 是 Python 中最受欢迎的网络爬虫框架之一&#xff0c;专为大规模网络爬取和数据提取任务而设计。它功能强大、…...

GEC6818初次连接使用

目录 1.开发板资源接口​编辑​编辑 2.安装 SecureCRT工具 2.1SecureCRT相关问题 3.连接开发板 4.开发板文件传输 4.1串口传输 rx 从电脑下载文件到开发板 sz 从开发板把文件发送到电脑 4.2U盘/SD卡传输 4.3网络传输[重点] 5.运行传到开发板的可执行文件 6.开发板网络…...

解释下不同Gan模型之间的异同点

生成对抗网络&#xff08;GAN, Generative Adversarial Network&#xff09;是一类强大的生成模型。随着时间的推移&#xff0c;研究人员提出了许多不同的 GAN 变体来改善原始模型的性能或针对特定任务进行优化。下面将解释一些常见的 GAN 变体&#xff0c;并讨论它们的异同点。…...

Hadoop的一些高频面试题 --- hdfs、mapreduce以及yarn的面试题

文章目录 一、HDFS1、Hadoop的三大组成部分2、本地模式和伪分布模式的区别是什么3、什么是HDFS4、如何单独启动namenode5、hdfs的写入流程6、hdfs的读取流程7、hdfs为什么不能存储小文件8、secondaryNameNode的运行原理9、hadoop集群启动后离开安全模式的条件10、hdfs集群的开机…...

Day99 代码随想录打卡|动态规划篇--- 01背包问题

题目&#xff08;卡玛网T46&#xff09;&#xff1a; 小明是一位科学家&#xff0c;他需要参加一场重要的国际科学大会&#xff0c;以展示自己的最新研究成果。他需要带一些研究材料&#xff0c;但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等&am…...

往证是什么意思

“往证”通常是在数学证明中使用的一种方法&#xff0c;尤其是在证明某个结论的相反&#xff08;即否定&#xff09;是错误的情况下。具体来说&#xff0c;就是假设结论不成立&#xff0c;然后通过逻辑推理展示出这种假设导致矛盾&#xff0c;从而得出原结论必然成立。 举例说…...

Camunda流程引擎并发性能优化

文章目录 Camunda流程引擎一、JobExecutor1、工作流程2、主要作用 二、性能问题1、实际场景&#xff1a;2、性能问题描述3、总结 三、优化方案方案一&#xff1a;修改 Camunda JobExecutor 源码以实现租户 ID 隔离方案二&#xff1a;使用 max-jobs-per-acquisition 参数控制上锁…...

spring springboot 日志框架

一、常见的日志框架 JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j.... 注意&#xff1a;SLF4j 类似于接口 Log4j &#xff0c;Logback 都是出自同一作者之手 JUL 为apache 公司产品 Spring&#xff08;commons-logging&#xff09;、Hibernate&#xff08;jboss…...

【D3.js in Action 3 精译_022】3.2 使用 D3 完成数据准备工作

当前内容所在位置 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可视化最佳实践&#xff08;下&#xff09;1.4 本章小结 第二章…...

电脑怎么禁用软件?5个方法速成,小白必入!

电脑禁用软件的方法多种多样&#xff0c;以下是五种简单易行的方法. 适合不同需求的用户&#xff0c;特别是电脑小白。 1. 使用任务管理器禁用启动项 操作步骤&#xff1a;按下“Ctrl Shift Esc”组合键&#xff0c;打开任务管理器。 切换到“启动”选项卡&#xff0c;找到…...

力扣之181.超过经理收入的员工

文章目录 1. 181.超过经理收入的员工1.1 题干1.2 准备数据1.3 题解1.4 结果截图 1. 181.超过经理收入的员工 1.1 题干 表&#xff1a;Employee -------------------- | Column Name | Type | -------------------- | id | int | | name | varchar | | salary | int | | mana…...

C++语法应用:从return机制看返回指针,返回引用

前言 编程是极其注重实践的工作,学习的同时要伴随代码 引入 此前对返回指针和引用有一些纠结&#xff0c;从return角度来观察发生了什么。 return机制 函数中return表示代码结束&#xff0c;如果return后面有其他代码将不被执行。 return发生了值转移&#xff0c;return后面的…...

Linux5-echo,>,tail

1.echo命令 echo是输出命令&#xff0c;类似printf 例如&#xff1a;echo "hello world"&#xff0c;输出hello world echo pwd&#xff0c;输出pwd的位置。是键盘上~ 2.重定向符> >> >指把左边内容覆盖到右边 echo hello world>test.txt >…...

sqlgun靶场训练

1.看到php&#xff1f;id &#xff0c;然后刚好有个框&#xff0c;直接测试sql注入 2.发现输入1 union select 1,2,3#的时候在2处有回显 3.查看表名 -1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()# 4.查看列名…...

简化登录流程,助力应用建立用户体系

随着智能手机和移动应用的普及&#xff0c;用户需要在不同的应用中注册和登录账号&#xff0c;传统的账号注册和登录流程需要用户输入用户名和密码&#xff0c;这不仅繁琐而且容易造成用户流失。 华为账号服务(Account Kit)提供简单、快速、安全的登录功能&#xff0c;让用户快…...

【研发日记】嵌入式处理器技能解锁(六)——ARM的Cortex-M4内核

文章目录 前言 背景介绍 指令集架构 ARM起源 ARM分类 Cortex-M4 内核框架 指令流水线 实践应用 总结 参考资料 前言 见《【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法》 见《【研发日记】嵌入式处理器技能解锁(二)——TI C2000 DSP的SCI(…...

深度学习经典模型之T5

T5(Text-to-Text Transfer Transformer) 是继BERT之后Google的又外力作&#xff0c;它是一个文本到文本迁移的基于Transformer的NLP模型&#xff0c;通过将 所有任务统一视为一个输入文本并输出到文本(Text-to-Text)中&#xff0c;即将任务嵌入在输入文本中&#xff0c;用文本的…...

10.第二阶段x86游戏实战2-反编译自己的程序加深堆栈的理解

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

Spring Boot+Neo4j知识图谱实战:3步搭建智能关系网络!

一、引言 在数据驱动的背景下&#xff0c;知识图谱凭借其高效的信息组织能力&#xff0c;正逐步成为各行业应用的关键技术。本文聚焦 Spring Boot与Neo4j图数据库的技术结合&#xff0c;探讨知识图谱开发的实现细节&#xff0c;帮助读者掌握该技术栈在实际项目中的落地方法。 …...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

大数据学习(132)-HIve数据分析

​​​​&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...