前端按需加载有哪些实现方式和最佳实践?
按需加载维度
- 路由级:页面/路由按需加载
- 组件级:重组件按需加载
- 依赖级:大依赖按需加载
- 资源级:图片/字体按需加载
路由级懒加载
import { lazy, Suspense } from 'react'
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const routes = [
{ path: '/', element: <Suspense fallback={<Loader />}><Home /></Suspense> },
{ path: '/about', element: <Suspense fallback={<Loader />}><About /></Suspense> }
]
const routes = [
{ path: '/', component: () => import('./pages/Home.vue') },
{ path: '/about', component: () => import('./pages/About.vue') }
]
组件级懒加载
const HeavyChart = lazy(() => import('./HeavyChart'))
function Dashboard() {
const [showChart, setShowChart] = useState(false)
return (
<>
<button onClick={() => setShowChart(true)}>显示图表</button>
{showChart && (
<Suspense fallback={<Loader />}>
<HeavyChart />
</Suspense>
)}
</>
)
}
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))
const HeavyChart = defineAsyncComponent({
loader: () => import('./HeavyChart.vue'),
loadingComponent: Loader,
errorComponent: Error,
delay: 200,
timeout: 10000
})
依赖级懒加载
async function exportPDF() {
const { jsPDF } = await import('jspdf')
const doc = new jsPDF()
doc.text('Hello', 10, 10)
doc.save('output.pdf')
}
async function openEditor() {
const monaco = await import('monaco-editor')
monaco.editor.create(document.getElementById('container'))
}
async function renderChart() {
const echarts = await import('echarts')
const chart = echarts.init(document.getElementById('chart'))
chart.setOption(option)
}
资源级懒加载
<img loading="lazy" src="image.jpg" />
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})
document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img))
预加载策略
<Link
to="/dashboard"
onMouseEnter={() => import('./pages/Dashboard')}
>
function prefetchRoute(routeImport) {
requestIdleCallback(() => routeImport())
}
import(
'./Dashboard'
)
<link rel="prefetch" href="/dashboard.js">
<link rel="preload" href="/critical.js" as="script">
代码分割策略
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['antd', '@emotion/react'],
'chart-vendor': ['echarts', 'd3']
}
}
}
}
}
const Dashboard = lazy(() => import('./Dashboard'))
按需加载的 UI 优化
<Suspense fallback={<Skeleton />}>
<HeavyComponent />
</Suspense>
const LazyComp = defineAsyncComponent({
loader: () => import('./HeavyComp.vue'),
loadingComponent: Loader,
delay: 200
})
const LazyComp = defineAsyncComponent({
loader: () => import('./HeavyComp.vue'),
errorComponent: ErrorComponent,
timeout: 10000
})
性能指标影响
| 策略 |
首屏 JS |
交互体验 |
复杂度 |
| 全量加载 |
大 |
好 |
低 |
| 路由懒加载 |
小 |
首次进入需等待 |
中 |
| 路由 + 预加载 |
小 |
好 |
高 |
| 组件懒加载 |
最小 |
交互时需等待 |
高 |
最佳实践
- 路由全部懒加载:减小首屏体积
- 大依赖动态导入:图表、编辑器、PDF
- 合理预加载:hover/空闲时预加载下一页
- 骨架屏:提升加载感知速度
- 按需加载第三方库:lodash-es、antd 按需引入
- 分析包体积:
vite-bundle-visualizer / webpack-bundle-analyzer
同分类其他题目