前端全量资源预加载优化指南(React内置API + Vue实现 + prerender/prefetch深度对比)
前言
在SPA单页应用中,网络请求链路(DNS→TCP→TLS→资源下载→脚本执行)是页面白屏、路由卡顿的核心元凶。浏览器提供了一套原生预优化能力,React18直接封装成react-dom可调用API,Vue无内置函数,需基于原生标签手动封装。
本文一次性覆盖:DNS预解析、预连接、资源预加载、脚本预执行、ESM模块、低优预获取、整页预渲染全套能力,附带包含关系、组合写法、避坑、面试题、背诵速记
一、React18 内置六大预加载API
导入方式
js
import {
prefetchDns,
preConnect,
preLoad,
preInit,
preloadModule,
preinitModule,
} from 'react-dom';
1. prefetchDns DNS预解析
- 作用:仅将域名解析为IP地址,最轻量网络操作
- 无连接、无握手,只缓存DNS记录
- 适用:第三方广告、埋点、低优先级次要域名
js
prefetchDns('https://analytics.xxx.com');
2. preConnect 预连接
- 完整流程:DNS解析 + TCP三次握手 + TLS加密握手
- 天然包含prefetchDns能力
- 适用:首屏接口、CDN静态资源、马上要访问的核心域名
js
preConnect('https://api.business.com');
避坑:同一个域名不要同时写prefetchDns+preConnect,属于冗余代码。
3. preLoad 通用资源预加载
- 行为:下载资源存入缓存,不解析、不执行
- 支持类型:script/image/font/style/audio/video
- 高优先级,供给当前页面立刻要用的关键资源
js
preLoad('/font.woff2', { as: 'font' });
preLoad('/banner.png', { as: 'image' });
4. preInit 普通JS脚本预初始化
- 等价逻辑:
preLoad(script) + 下载后自动执行脚本 - 完全包含preLoad能力
- 仅支持普通UMD/普通script脚本,不支持ESM
- 适用:全局监控SDK、埋点、工具库
js
preInit('https://cdn/track-sdk.js');
5. preloadModule ESM模块预加载
- 专门针对
.mjs/type="module"ES模块 - 只下载缓存,不会运行代码
- 对标原生
<link rel="modulepreload">
js
preloadModule('/sub-page.mjs', { crossOrigin: 'anonymous' });
6. preinitModule ESM模块预初始化
- 等价逻辑:
preloadModule + 下载完成自动执行ESM - 包含preloadModule全部下载能力
- 适用于模块化全局SDK、核心底层模块
js
preinitModule('/core-sdk.mjs', { crossOrigin: 'anonymous' });
六大API包含关系总结
- preConnect ⊃ prefetchDns
- preInit ⊃ preLoad
- preinitModule ⊃ preloadModule
统一对比表格
| API | 包含能力 | 支持资源 | 是否自动执行 | 优先级 |
|---|---|---|---|---|
| prefetchDns | DNS解析 | 域名 | 仅解析 | 极低 |
| preConnect | DNS+TCP+TLS | 域名 | 建立长连接 | 低 |
| preLoad | 下载缓存 | 图片/字体/CSS/普通JS | ❌ 不执行 | 高 |
| preInit | 下载+运行 | 普通JS脚本 | ✅ 自动执行 | 高 |
| preloadModule | ESM下载缓存 | ES Module | ❌ 不执行 | 高 |
| preinitModule | ESM下载+运行 | ES Module | ✅ 自动执行 | 高 |
二、低优先级后台预获取 prefetch
1. 原生底层
对应 <link rel="prefetch">,React无内置封装API,需动态创建link实现。
2. 核心特性
- 浏览器完全空闲、带宽富余才下载,优先级远低于preLoad
- 仅下载单文件缓存,不解析、不运行、不渲染页面
- 专门用于未来大概率跳转的下一页分包资源
React封装使用
js
const cache = new Set();
const preFetch = (href) => {
if (cache.has(href)) return;
cache.add(href);
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = href;
document.head.appendChild(link);
};
// 鼠标悬浮菜单预拉取下一页分包
const hoverCart = () => preFetch('/cart-chunk.js');
prefetch vs preLoad 关键区分
| 对比 | prefetch | preLoad |
|---|---|---|
| 优先级 | 极低后台闲时 | 高优先级优先加载 |
| 使用场景 | 后续路由、未来页面 | 当前页面必备资源 |
| 带宽抢占 | 完全不抢占首屏 | 优先分配带宽 |
| 运行行为 | 只缓存不解析执行 | 只缓存不解析执行 |
三、整页预渲染 prerender
1. 原生底层
<link rel="prerender">,React/Vue均无框架API,动态创建link调用。
2. 执行逻辑
浏览器后台开启隐藏页面,完整走一遍全流程:DNS→建连→拉取页面全部资源→解析HTML/CSS→执行JS→渲染布局生成完整页面快照;跳转时直接展示成品页面,零白屏。
3. 开销定位
所有预优化里资源开销最大(带宽、CPU、内存占用高),优先级最低,仅浏览器极度空闲才会启动。
封装调用
js
function prerenderPage(url) {
const link = document.createElement('link');
link.rel = 'prerender';
link.href = url;
document.head.appendChild(link);
}
// 空闲时预渲染必访问页面
requestIdleCallback(() => prerenderPage('/pay'));
prerender vs prefetch
| 对比 | prefetch | prerender |
|---|---|---|
| 处理对象 | 单个js/css/img文件 | 完整页面+页面所有附属资源 |
| 执行力度 | 仅下载缓存 | 加载+执行JS+完整渲染页面 |
| 性能开销 | 小 | 极大 |
| 打开速度 | 跳转后仍需解析脚本 | 跳转瞬间展示成品 |
关键避坑点
- 副作用提前执行问题
页面埋点、接口请求、定时器、弹窗会在预渲染阶段触发,造成统计失真。通过document.prerendering判断屏蔽:
js
// 预渲染环境不执行业务副作用
if (!document.prerendering) {
sendReport();
fetchUserInfo();
}
- 移动端弱网、低端机型慎用,容易拖慢当前页面
- 一次只预渲染一个页面,禁止多页面并行prerender
- 只适合无参固定路由,动态参数页面无法复用预渲染结果
- 和构建工具
prerender-spa-plugin静态预渲染不是同一技术:- link prerender:浏览器运行时临时后台渲染,缓存随页面关闭销毁
- 打包预渲染:构建阶段生成静态HTML文件,部署至服务端长期使用
四、Vue 体系等效实现方案
Vue没有React式可直接调用的预加载JS API,全部依托浏览器原生标签实现,搭配Webpack/Vite编译标记优化。
1. DNS预解析(等同prefetchDns)
html
<!-- index.html全局静态 -->
<link rel="dns-prefetch" href="https://api.xxx.com" />
2. 预连接(等同preConnect)
html
<link rel="preconnect" href="https://cdn.xxx.com" crossorigin>
3. 资源预加载(等同preLoad)
html
<link rel="preload" href="/font.woff2" as="font" crossorigin>
4. 脚本预执行(等同preInit)
手动创建script标签加载并运行JS
js
function preInitScript(src) {
const script = document.createElement('script');
script.src = src;
document.head.appendChild(script);
}
5. ESM模块系列
- Vite自动插入
modulepreload; - 手动:
<link rel="modulepreload" href="/page.mjs">; - 执行ESM:创建
script type="module"标签。
6. Webpack专属路由预取标记
js
// 低优后台预获取(等同prefetch)
const Cart = () => import(/* webpackPrefetch: true */ './Cart.vue')
// 高优预加载(等同preLoad)
const Checkout = () => import(/* webpackPreload: true */ './Checkout.vue')
Vue短板补充
- 无框架自动去重,封装函数必须加
Set缓存防止重复插入link; - 无内置空闲调度,精细优化需要手动搭配
requestIdleCallback; - Vite对ESM modulepreload工程自动化程度高于Webpack/React。
五、全场景最佳组合写法
js
import { preConnect, preLoad, preInit, preloadModule } from 'react-dom';
// 1. 核心业务域名提前建连(自带DNS解析)
preConnect('https://api.business.com');
preConnect('https://cdn.static.com');
// 2. 第三方次要域名仅轻量DNS解析
prefetchDns('https://ads.xxx.com');
// 3. 当前页面关键静态资源
preLoad('/logo.png', { as: 'image' });
preLoad('/main-font.woff2', { as: 'font' });
// 4. 全局普通JS SDK下载并执行
preInit('https://cdn/monitor-sdk.js');
// 5. 路由懒加载ESM分包,只缓存不执行
preloadModule('/order.mjs');
// 6. 鼠标悬浮预判:低优预取下一页资源
const hoverNext = () => preFetch('/user-chunk.js');
// 7. 极高必访页面,空闲整页预渲染
requestIdleCallback(() => prerenderPage('/cart'));
六、通用重要注意事项
- 遵循包含关系,同一域名/资源不要叠加调用(如preConnect旁不要再写prefetchDns);
- preLoad优先级高,只给当前页面核心资源,不要滥用抢占带宽;
- preConnect浏览器并发连接数有限(常规6个),批量第三方域名只用prefetchDns;
- preInit/preinitModule会执行代码,仅用于必须提前初始化的脚本;
- prerender开销巨大,移动端弱网、低访问概率页面禁止使用;
- React内部自动对同一URL去重,Vue必须手动缓存去重;
- prefetchDns ≠ prefetch:一个只解析IP,一个下载完整文件,名字相似能力完全无关。
七、高频面试真题(含标准答案)
1、preConnect和prefetchDns关系?能否同域名一起使用?
答:preConnect内部完整包含DNS解析能力,同域名同时调用属于冗余;马上要用的域名直接用preConnect,仅潜在访问的第三方域名用prefetchDns分层。
2、preLoad与preInit区别?
答:preLoad仅下载缓存所有类型资源,不执行脚本;preInit针对普通JS,下载完成自动运行,preInit包含preLoad下载逻辑。
3、preloadModule、preinitModule适用场景?
答:专门适配ES Module;preloadModule只下载不执行,用于路由懒加载分包;preinitModule下载并运行,用于模块化全局SDK。
4、prefetch和preload核心差异?
答:preload高优先级服务当前页面;prefetch极低空闲优先级,后台预缓存未来页面资源。
5、prerender和prefetch区别,prerender埋点重复上报怎么解决?
答:prefetch只下载单个文件;prerender完整渲染整页,开销更大。通过document.prerendering判断,预渲染环境屏蔽接口、埋点、定时器等副作用。
6、Vue有没有和React一一对应的预加载API?
答:没有。Vue依托原生link/script标签、webpack注释标记实现同等能力,需要手动封装去重与空闲调度。
八、速记背诵版
- 域名层:prefetchDns只解析IP,preConnect=DNS+TCP+TLS;
- 资源层:preLoad只下载,preLoad+执行=preInit(普通JS);
- ESM层:preloadModule只下载,preloadModule+执行=preinitModule;
- 未来页面单文件:prefetch低优后台缓存;
- 极高必访整页:prerender完整后台渲染,开销最大;
- React内置函数一键调用自动去重,Vue原生标签手动封装;
- 同资源不重复调用,核心域名用preConnect,第三方轻量DNS解析;
- prerender判断
document.prerendering规避埋点重复上报。
九、文末总结
整套预加载体系本质是把网络耗时从用户交互等待阶段,前置到浏览器空闲阶段 。React18把原生能力封装成易用API大幅降低接入成本,Vue虽无内置封装,但依托原生标准也可实现同等优化效果。
优化优先级:先域名连接优化 → 再当前页面关键资源 → 最后预判路由预取/预渲染,拒绝无脑滥用高开销API,平衡性能与设备带宽体验。