前端按需加载有哪些实现方式和最佳实践?

按需加载维度

  1. 路由级:页面/路由按需加载
  2. 组件级:重组件按需加载
  3. 依赖级:大依赖按需加载
  4. 资源级:图片/字体按需加载

路由级懒加载

// React
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> }
]

// Vue
const routes = [
  { path: '/', component: () => import('./pages/Home.vue') },
  { path: '/about', component: () => import('./pages/About.vue') }
]

组件级懒加载

// React
const HeavyChart = lazy(() => import('./HeavyChart'))

function Dashboard() {
  const [showChart, setShowChart] = useState(false)
  return (
    <>
      <button onClick={() => setShowChart(true)}>显示图表</button>
      {showChart && (
        <Suspense fallback={<Loader />}>
          <HeavyChart />
        </Suspense>
      )}
    </>
  )
}

// Vue
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
})

依赖级懒加载

// PDF 导出(jspdf 很大,只在需要时加载)
async function exportPDF() {
  const { jsPDF } = await import('jspdf')
  const doc = new jsPDF()
  doc.text('Hello', 10, 10)
  doc.save('output.pdf')
}

// 编辑器(monaco-editor 很大)
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" />

// IntersectionObserver
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))

预加载策略

// 1. 用户 hover 链接时预加载
<Link 
  to="/dashboard"
  onMouseEnter={() => import('./pages/Dashboard')}
>

// 2. 空闲时预加载
function prefetchRoute(routeImport) {
  requestIdleCallback(() => routeImport())
}

// 3. Webpack magic comments
import(
  /* webpackChunkName: "dashboard" */
  /* webpackPrefetch: true */  // 浏览器空闲时加载
  './Dashboard'
)

// 4. HTML 标签
<link rel="prefetch" href="/dashboard.js">
<link rel="preload" href="/critical.js" as="script">

代码分割策略

// Vite 手动分块
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'))
// → 生成单独的 dashboard.[hash].js

按需加载的 UI 优化

// 1. 骨架屏
<Suspense fallback={<Skeleton />}>
  <HeavyComponent />
</Suspense>

// 2. 延迟显示 loading(避免快速加载时闪烁)
const LazyComp = defineAsyncComponent({
  loader: () => import('./HeavyComp.vue'),
  loadingComponent: Loader,
  delay: 200  // 200ms 后才显示 loading
})

// 3. 错误处理
const LazyComp = defineAsyncComponent({
  loader: () => import('./HeavyComp.vue'),
  errorComponent: ErrorComponent,
  timeout: 10000
})

性能指标影响

策略 首屏 JS 交互体验 复杂度
全量加载 大 好 低
路由懒加载 小 首次进入需等待 中
路由 + 预加载 小 好 高
组件懒加载 最小 交互时需等待 高

最佳实践

  1. 路由全部懒加载:减小首屏体积
  2. 大依赖动态导入:图表、编辑器、PDF
  3. 合理预加载:hover/空闲时预加载下一页
  4. 骨架屏:提升加载感知速度
  5. 按需加载第三方库:lodash-es、antd 按需引入
  6. 分析包体积:vite-bundle-visualizer / webpack-bundle-analyzer

同分类其他题目