阅读视图

发现新文章,点击刷新页面。

前端优化实战指南(工程化/首屏/懒加载/Next.js等)

构建优化是前端优化的基础,也是性价比最高的优化方向——无需大量修改业务代码,就能显著降低打包体积、提升构建速度,适配不同环境的部署需求,同时适配 React、Vue2、Vue3 等主流框架。


一、工程化 & 构建优化(Webpack / Vite)

1.1 Webpack 优化(主流项目实战,适配 React/Vue2/Vue3)

1.1.1 减小打包体积(核心:tree-shaking + 代码分割 + 依赖优化)

  • 开启 tree-shaking:仅打包被使用的代码,剔除死代码。

    • 配置:mode: "production"(默认开启),配合 package.json"sideEffects": false(需确认第三方依赖无副作用,若有则单独配置,如 ["*.css", "*.less", "*.scss"])。
    • 注意:仅对 ES6 模块(import/export)有效,CommonJS 模块(require)无法触发 tree-shaking,需避免混用模块规范;Vue2 项目需确保使用 vue-loader@15+ 版本,React 项目需避免使用 require 引入组件/工具。
  • 代码分割(Code Splitting):将代码拆分为多个 chunk,避免单文件过大,实现按需加载。

    • 路由分割

      • React 项目:使用 React.lazy + Suspense(函数组件),配合 react-router-dom 实现路由拆分,Suspense 需配置 fallback(加载占位),避免页面空白;类组件可使用 loadable-components 替代。

        // React 路由分割示例
        import { Suspense, lazy } from 'react';
        import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
        
        const Home = lazy(() => import('./pages/Home'));
        const About = lazy(() => import('./pages/About'));
        
        function App() {
          return (
            <Router>
              <Suspense fallback={<div>加载中...</div>}>
                <Routes>
                  <Route path="/" element={<Home />} />
                  <Route path="/about" element={<About />} />
                </Routes>
              </Suspense>
            </Router>
          );
        }
        
      • Vue2 项目:使用 vue-routercomponent: () => import('xxx'),配合 webpackChunkName 自定义 chunk 名称,便于调试。

        // Vue2 路由分割示例(vue-router@3.x)
        const router = new VueRouter({
          routes: [
            {
              path: '/',
              name: 'Home',
              component: () => import(/* webpackChunkName: "home" */ './views/Home.vue')
            },
            {
              path: '/about',
              name: 'About',
              component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
            }
          ]
        });
        
      • Vue3 项目:与 Vue2 用法一致,适配 vue-router@4.x,可结合 setup 语法使用,无额外配置差异。

    • 公共依赖分割splitChunks 配置,将第三方依赖(如 react、react-dom、vue、vue-router、axios)与业务代码分离,单独打包为 vendor chunk,利用浏览器缓存复用。

      // webpack.config.js
      optimization: {
        splitChunks: {
          chunks: 'all',
          cacheGroups: {
            vendor: {
              test: /[\\/]node_modules[\\/]/,
              name: 'vendors',
              priority: 10,
              reuseExistingChunk: true
            },
            common: {
              minSize: 30000,
              minChunks: 2,
              priority: 5,
              reuseExistingChunk: true
            }
          }
        }
      }
      
  • 依赖优化:剔除无用依赖 + 替换轻量依赖

    • 使用 webpack-bundle-analyzer 分析打包体积,找到体积过大的依赖。

    • 替换方案:moment.jsday.js(体积缩小 80%+)、lodashlodash-es(支持 tree-shaking)、jquery → 原生 DOM / 轻量库。

    • 按需引入:

      • React 生态:antd、Material-UI 等 UI 库,使用 babel-plugin-import 实现组件和样式的按需加载。
      • Vue2 生态:element-ui 使用 babel-plugin-import 按需引入。
      • Vue3 生态:element-plusant-design-vue@4+ 支持 babel-plugin-import 按需引入,也可通过 setup 语法自动按需引入组件。
      // .babelrc(React + antd 按需引入)
      {
        "plugins": [
          ["import", {
            "libraryName": "antd",
            "libraryDirectory": "es",
            "style": "css"
          }]
        ]
      }
      
      // .babelrc(Vue2 + element-ui 按需引入)
      {
        "plugins": [
          ["import", {
            "libraryName": "element-ui",
            "libraryDirectory": "lib",
            "style": true
          }]
        ]
      }
      
  • 资源压缩

    • JS 压缩:production 模式默认使用 TerserPlugin,可配置 parallel: true 开启多线程压缩。

    • CSS 压缩:使用 mini-css-extract-plugin 提取 CSS 为单独文件,配合 css-minimizer-webpack-plugin 压缩 CSS。

    • 图片压缩:使用 image-webpack-loader 压缩图片,配置 limit 限制小图片转为 base64(减少 HTTP 请求)。

      // module.rules 中配置
      {
        test: /\.(png|jpe?g|gif|svg)$/i,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 8192,
              name: 'static/img/[name].[hash:8].[ext]',
              esModule: false
            }
          },
          'image-webpack-loader'
        ]
      }
      

1.1.2 提升构建速度

  • 多线程构建:使用 thread-loader 将耗时的 loader(如 babel-loaderts-loader)放入单独线程。

    // React 项目配置
    {
      test: /\.(js|jsx|ts|tsx)$/,
      exclude: /node_modules/,
      use: [
        'thread-loader',
        {
          loader: 'babel-loader',
          options: {
            cacheDirectory: true
          }
        }
      ]
    }
    
    // Vue 项目配置(thread-loader 放在 vue-loader 之前)
    {
      test: /\.vue$/,
      exclude: /node_modules/,
      use: [
        'thread-loader',
        'vue-loader'
      ]
    }
    
  • 缓存优化:开启 loader 缓存(cacheDirectory)和 webpack 持久化缓存(cache: { type: 'filesystem' }),避免每次构建都重新编译。

  • 缩小构建范围exclude 排除 node_modulesdist 等无需编译的目录;include 明确指定需要编译的目录(如 src)。

  • 替换构建工具:若项目体积较大,可考虑将 Webpack 替换为 Vite(基于 ES Module,冷启动速度提升 10 倍+)。Vue3 项目优先使用 Vite,React 项目可使用 @vitejs/plugin-react 适配。

1.2 Vite 优化(新兴项目首选,适配 React/Vue2/Vue3)

Vite 本身已做了大量优化,核心优化方向是"减少不必要的编译":

  • 依赖预构建:Vite 自动预构建第三方依赖,生成 ESM 格式产物,可通过 optimizeDeps 自定义预构建范围。

  • 静态资源优化:内置图片、CSS 压缩,小图片自动转 base64,可通过 assetsInclude 配置。

  • 生产环境优化build 时默认开启 minify: 'terser',配置 rollupOptions 实现代码分割。

    // vite.config.js(Vue3 项目)
    import { defineConfig } from 'vite';
    import vue from '@vitejs/plugin-vue';
    
    export default defineConfig({
      plugins: [vue()],
      build: {
        minify: 'terser',
        rollupOptions: {
          output: {
            chunkFileNames: 'static/js/[name].[hash].js',
            entryFileNames: 'static/js/[name].[hash].js',
            assetFileNames: 'static/[ext]/[name].[hash].[ext]',
            manualChunks: {
              vendor: ['vue', 'vue-router', 'axios']
            }
          }
        }
      }
    });
    
    // vite.config.js(React 项目)
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    
    export default defineConfig({
      plugins: [react()],
      build: {
        minify: 'terser',
        rollupOptions: {
          output: {
            chunkFileNames: 'static/js/[name].[hash].js',
            entryFileNames: 'static/js/[name].[hash].js',
            assetFileNames: 'static/[ext]/[name].[hash].[ext]',
            manualChunks: {
              vendor: ['react', 'react-dom', 'react-router-dom']
            }
          }
        }
      }
    });
    
  • 框架适配:Vue2 需安装 @vitejs/plugin-vue2;React 需安装 @vitejs/plugin-react(支持 Fast Refresh);Vue3 原生支持。


二、首屏加载优化(核心:减少加载时间,提升用户感知)

首屏加载速度直接影响用户留存,核心思路是"减少首屏资源体积、减少 HTTP 请求、优化资源加载顺序"。

2.1 资源层面优化

  • HTML 优化

    • 精简 HTML 结构,将首屏关键 CSS 内联到 <head>,JS 脚本放在 <body> 底部或使用 defer/async 属性。
    • React 项目:使用 react-dom/server 或 Next.js 实现服务端渲染,减少白屏时间。
    • Vue 项目:使用 vue-server-renderer(Vue2)、@vue/server-renderer(Vue3)或 Nuxt.js 实现 SSR。
  • CSS 优化

    • 提取首屏关键 CSS(Critical CSS)内联到 HTML,非关键 CSS 异步加载。
    • 使用 CSS Sprites 合并小图标,避免使用 @import 引入 CSS(会阻塞渲染)。
    • React 项目:使用 CSS Modules 或 Styled Components 避免样式冲突。
    • Vue 项目:使用 scoped 样式或 CSS Modules 减少样式冗余。
  • JS 优化

    • 减少首屏 JS 体积,非必要脚本(统计、广告)异步加载。
    • React 项目:使用 React.lazy + Suspense 拆分首屏组件,减少 useEffect 的不必要执行。
    • Vue2 项目:使用路由懒加载,用 v-if 替代 v-show(首屏不显示的组件不渲染)。
    • Vue3 项目:使用 setup 语法提升响应式效率,配合 Teleport 将非首屏组件挂载到主渲染树外。

2.2 框架专属首屏优化

2.2.1 React 项目

  • SSR/SSG:使用 Next.js,通过 getStaticProps(SSG)或 getServerSideProps(SSR)提前获取数据,首屏由服务端返回完整 HTML。
  • 预加载:使用 Next.js Link 组件的 prefetch 属性预加载路由;使用 dynamic import 动态加载非首屏组件。
  • 状态管理:首屏无需的状态延迟初始化,使用 useMemouseCallback 缓存计算结果和函数。

2.2.2 Vue2 项目

  • SSR:使用 Nuxt.js@2,通过 asyncDatafetch 提前获取首屏数据。
  • Vue 实例优化:避免在 createdmounted 中执行耗时操作,可延迟到 $nextTicksetTimeout
  • 组件优化:首屏组件精简,非首屏组件使用路由懒加载;避免使用 Vue.filter(全局过滤器会增加初始化时间)。

2.2.3 Vue3 项目

  • SSR/SSG:使用 Nuxt.js@3 或 VitePress,通过 useAsyncDatauseFetch 提前获取数据。
  • Composition API 优化setup 语法减少组件初始化时间;避免在 setup 中执行耗时操作,使用 onMounted 延迟执行。
  • 按需引入:Vue3 核心库可按需引入;Pinia 替代 Vuex(体积更小、性能更优)。

三、懒加载优化(通用+框架适配,减少首屏压力)

懒加载核心是"按需加载",仅当资源进入视口或即将进入视口时才加载。

3.1 图片懒加载

  • 原生懒加载:使用 <img loading="lazy"> 属性,浏览器原生支持,无需额外插件。不支持 IE,可做降级处理。

  • 插件懒加载(适配框架)

    • React 项目:使用 react-lazyload 或自定义 Hook(IntersectionObserver API)。

      // React 自定义懒加载 Hook
      import { useEffect, useRef, useState } from 'react';
      
      function useLazyLoad() {
        const ref = useRef(null);
        const [isVisible, setIsVisible] = useState(false);
      
        useEffect(() => {
          const observer = new IntersectionObserver(
            ([entry]) => setIsVisible(entry.isIntersecting),
            { threshold: 0.1 }
          );
          if (ref.current) observer.observe(ref.current);
          return () => {
            if (ref.current) observer.unobserve(ref.current);
          };
        }, []);
      
        return { ref, isVisible };
      }
      
      // 使用示例
      function LazyImage({ src, alt }) {
        const { ref, isVisible } = useLazyLoad();
        return (
          <div ref={ref}>
            {isVisible ? (
              <img src={src} alt={alt} />
            ) : (
              <div className="placeholder" />
            )}
          </div>
        );
      }
      
    • Vue2 项目:使用 vue-lazyload 插件。

      // Vue2 配置 vue-lazyload
      import Vue from 'vue';
      import VueLazyload from 'vue-lazyload';
      
      Vue.use(VueLazyload, {
        preLoad: 1.3,
        error: 'error.png',
        loading: 'loading.gif',
        attempt: 1
      });
      
      <!-- 组件中使用 -->
      <img v-lazy="imageUrl" />
      
    • Vue3 项目:使用 vue3-lazyload 插件。

      // Vue3 配置 vue3-lazyload
      import { createApp } from 'vue';
      import App from './App.vue';
      import VueLazyload from 'vue3-lazyload';
      
      const app = createApp(App);
      app.use(VueLazyload, {
        preLoad: 1.3,
        error: 'error.png',
        loading: 'loading.gif'
      });
      app.mount('#app');
      
      <!-- 组件中使用 -->
      <img v-lazy="imageUrl" />
      
  • 注意事项:懒加载图片需设置宽高避免布局抖动;优先使用 WebP 格式(体积更小)并做降级处理;首屏可见的图片不要使用懒加载。

3.2 组件/路由懒加载

3.2.1 React 组件/路由懒加载

  • 路由懒加载:使用 React.lazy + Suspense(参见 1.1.1)。

  • 组件懒加载:非首屏组件使用 dynamic import 动态加载。

    // React 组件懒加载示例
    import { Suspense, lazy, useState } from 'react';
    
    const ModalComponent = lazy(() => import('./components/ModalComponent'));
    
    function Home() {
      const [showModal, setShowModal] = useState(false);
      return (
        <div>
          <button onClick={() => setShowModal(true)}>打开弹窗</button>
          {showModal && (
            <Suspense fallback={<div>加载中...</div>}>
              <ModalComponent onClose={() => setShowModal(false)} />
            </Suspense>
          )}
        </div>
      );
    }
    

3.2.2 Vue2/Vue3 组件/路由懒加载

  • 路由懒加载:使用 component: () => import('xxx')(参见 1.1.1)。

  • 组件懒加载:

    // Vue2 组件懒加载
    export default {
      components: {
        LazyComponent: () => import(
          /* webpackChunkName: "lazy-component" */
          './LazyComponent.vue'
        )
      }
    };
    
    // Vue3 组件懒加载(setup 语法)
    import { defineAsyncComponent } from 'vue';
    
    const LazyComponent = defineAsyncComponent(
      () => import('./LazyComponent.vue')
    );
    
    export default {
      components: { LazyComponent }
    };
    

3.3 第三方资源懒加载

  • 第三方脚本(统计、广告、地图)异步加载,避免阻塞首屏渲染。

    // 动态加载第三方脚本
    function loadScript(url, callback) {
      var script = document.createElement('script');
      script.src = url;
      script.async = true;
      script.onload = callback;
      document.body.appendChild(script);
    }
    
    // 页面加载完成后再加载统计脚本
    window.addEventListener('load', function () {
      loadScript('https://analytics.example.com/sdk.js', function () {
        console.log('统计脚本加载完成');
      });
    });
    
  • React/Vue 项目:第三方组件(如 echarts)使用懒加载引入,避免首屏加载冗余资源。


四、Next.js 优化(React 框架专属)

Next.js 内置了大量优化特性,在此基础上补充实战优化方案。

4.1 渲染模式优化

  • SSG(静态站点生成):适用于静态页面(官网、文档),构建时生成 HTML,可部署到 CDN,首屏最快。

  • SSR(服务端渲染):适用于动态页面(用户中心、数据看板),每次请求由服务端渲染,SEO 友好。

  • ISR(增量静态再生):结合 SSG 和 SSR,构建时生成静态页面,定期重新生成。

    // Next.js ISR 示例
    export async function getStaticProps() {
      const res = await fetch('https://api.example.com/news');
      const data = await res.json();
      return {
        props: { data },
        revalidate: 60  // 每 60 秒重新生成页面
      };
    }
    

4.2 路由优化

  • 路由预加载Link 组件默认预加载视口内的路由(prefetch: true),可手动预加载。

    import Link from 'next/link';
    import { useRouter } from 'next/router';
    
    function Home() {
      const router = useRouter();
    
      const preloadAbout = () => {
        router.prefetch('/about');
      };
    
      return (
        <div>
          <Link href="/about" prefetch={true}>关于我们</Link>
          <button onClick={preloadAbout}>预加载关于我们</button>
        </div>
      );
    }
    
  • 动态路由优化:使用 getStaticPaths 配置预渲染路径;大量动态路径可设置 fallback: true,未预渲染的路径由服务端实时渲染。

4.3 资源优化

  • 图片优化:使用 Next.js 内置 Image 组件,自动压缩、格式转换、懒加载。

    import Image from 'next/image';
    
    function Home() {
      return (
        <Image
          src="/images/hero.jpg"
          alt="首页封面"
          width={1200}
          height={600}
          loading="lazy"
          quality={80}
        />
      );
    }
    
  • 字体优化:使用 Next.js 内置 Font 组件,预加载字体,避免 FOIT(字体闪烁)。

  • 脚本优化:使用 Script 组件,支持 beforeInteractiveafterInteractivelazyOnload 等加载策略。

4.4 运行时优化

  • 数据缓存:使用 SWR 或 React Query 缓存数据,减少重复请求。
  • 组件优化:使用 React.memouseMemouseCallback 避免不必要的重渲染。
  • 打包优化:通过 next.config.js 配置 optimization,开启代码分割和依赖优化。

五、运行时优化(React/Vue2/Vue3 通用)

运行时优化核心是"减少重渲染、提升交互响应速度"。

5.1 React 运行时优化

  • 减少重渲染

    import { memo, useMemo, useCallback, useState } from 'react';
    
    // 子组件:使用 memo 包裹,避免无意义重渲染
    const Child = memo(({ name, onClick }) => {
      console.log('子组件渲染');
      return <button onClick={onClick}>{name}</button>;
    });
    
    // 父组件
    function Parent() {
      const [count, setCount] = useState(0);
      const name = useMemo(() => `用户${count}`, [count]);
      const handleClick = useCallback(() => {
        console.log('点击事件');
      }, []);
    
      return (
        <div>
          <button onClick={() => setCount(count + 1)}>计数:{count}</button>
          <Child name={name} onClick={handleClick} />
        </div>
      );
    }
    
  • 事件优化:避免在 render 中创建内联函数,使用 useCallback 缓存事件处理函数;大量列表使用事件委托。

  • 数据处理优化:大量数据使用虚拟列表(react-windowreact-virtualized);耗时计算使用 Web Worker。

5.2 Vue2 运行时优化

  • 减少重渲染

    • 使用 v-once 只渲染一次不再更新的内容。
    • 使用 v-if 替代 v-show(不常显示的组件不创建 DOM)。
    • 减少 watch 监听范围,使用 computed 缓存计算属性。
  • 组件优化:拆分大型组件;使用 keep-alive 缓存路由组件。

    <!-- Vue2 keep-alive 示例 -->
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive" />
    </keep-alive>
    <router-view v-if="!$route.meta.keepAlive" />
    
    // 路由配置中设置 keepAlive
    const routes = [
      {
        path: '/home',
        component: Home,
        meta: { keepAlive: true }
      },
      {
        path: '/about',
        component: About,
        meta: { keepAlive: false }
      }
    ];
    
  • 数据优化:大量数据使用虚拟列表(vue-virtual-scroller);避免在 created/mounted 中执行耗时操作。

5.3 Vue3 运行时优化

  • 减少重渲染

    • 使用 refreactive 替代 Vue2 的 data,响应式效率更高。
    • 使用 computed 缓存计算属性。
    • 使用 watchEffect 替代 watch,自动追踪依赖。
    • 使用 definePropsdefineEmits 明确组件接口。
  • 组件优化keep-alive 缓存组件;Teleport 将弹窗等组件挂载到指定节点;拆分大型组件。

  • 数据优化:虚拟列表使用 vue-virtual-scroller@next;使用 toReftoRefs 避免解构导致响应式丢失;耗时计算使用 Web Worker。


六、网络 & 缓存优化(通用)

6.1 网络优化

  • HTTP 协议:使用 HTTPS;升级到 HTTP/2(多路复用、头部压缩)。
  • CDN 加速:静态资源部署到 CDN,用户就近获取。
  • 接口优化
    • 合并接口请求,避免重复请求。
    • 分页加载,避免一次性获取大量数据。
    • 使用接口缓存(localStorage/sessionStorage)缓存不常变化的数据。
    • React 可使用 SWR/React Query,Vue 可使用 vue-query。

6.2 缓存优化

  • 浏览器缓存

    • 强缓存:Cache-Control: public, max-age=86400,浏览器直接使用本地缓存。
    • 协商缓存:ETag/Last-Modified,强缓存过期后服务器判断资源是否更新,未更新返回 304。
  • 前端缓存

    • localStorage:持久化存储不常变化的数据。
    • sessionStorage:会话级临时数据。
    • Service Worker:缓存静态资源,实现离线访问(PWA),可通过 workbox 快速配置。
  • 缓存更新策略:静态资源使用哈希命名(app.[hash].js),资源更新时哈希变化触发重新请求;HTML 文件不缓存或短时间缓存,确保能获取最新资源引用。


七、总结

前端优化是系统性工作,需结合项目场景(React/Vue2/Vue3/Next.js)和业务需求,从工程化构建、首屏加载、懒加载、运行时、网络缓存等多个层面入手。

优先级建议:构建优化(tree-shaking、代码分割)> 路由懒加载 > 图片优化 > 首屏 SSR/SSG > 运行时优化 > 网络缓存优化。

验证工具:Lighthouse、Chrome DevTools Performance 面板、webpack-bundle-analyzer,持续检测优化效果。

❌