skip to content
月与羽

首屏优化

/ 8 min read


1. 代码分割 (Code Splitting) - 最核心的武器

这是解决此问题的最有效的方法。

  • 问题所在:默认情况下,像 Vite 或 Create React App 这样的打包工具会将你的所有应用代码(所有页面、所有组件)打包成一个或几个大的 JavaScript 文件。用户必须下载整个网站的 JS,才能看到一个页面

  • 解决方案:代码分割允许你将这个巨大的包拆分成许多小块(chunks)。最常见的分割方式是基于路由的分割。这意味着,用户访问主页时,他们只需要下载主页相关的代码,而关于页面、用户中心等页面的代码则在他们实际导航到这些页面时才会被下载。

  • 如何实现:React 内置了实现代码分割的工具:React.lazy()<Suspense>

示例:从常规导入切换到懒加载

之前的代码 (在 main.jsx 或路由配置文件中):

// 所有组件都被打包进主文件
import HomePage from './pages/HomePage';
import AboutPage from './pages.AboutPage';
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{ index: true, element: <HomePage /> },
{ path: "about", element: <AboutPage /> },
],
},
]);

改进后的代码 (使用 React.lazySuspense):

import React, { Suspense, lazy } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import App from './App';
// import Preloader from './components/Preloader'; // 一个简单的加载动画组件
// 1. 使用 React.lazy 进行动态导入
const HomePage = lazy(() => import('./pages/HomePage'));
const AboutPage = lazy(() => import('./pages/AboutPage'));
// 2. 在路由配置中使用这些懒加载的组件
const router = createBrowserRouter([
{
path: "/",
element: <App />,
children: [
{
index: true,
// 3. 用 Suspense 包裹组件,提供 fallback UI
element: (
<Suspense fallback={<div>Loading Page...</div>}>
<HomePage />
</Suspense>
),
},
{
path: "about",
element: (
<Suspense fallback={<div>Loading Page...</div>}>
<AboutPage />
</Suspense>
),
},
],
},
]);
// ... 渲染 RouterProvider

发生了什么?

  1. React.lazy() 告诉 React,这个组件是需要动态加载的。
  2. 当 React 尝试渲染 HomePage 时,它会触发网络请求去下载 HomePage.jsx 对应的 JS 文件。
  3. 在下载和解析这个新文件的期间,Suspense 组件会渲染你提供的 fallback 内容。这可以是简单的文本 Loading...,也可以是一个精美的骨架屏 (Skeleton Screen)
  4. 下载完成后,HomePage 组件会被无缝替换掉 fallback 内容。

效果:应用的初始 JS 包体积会急剧减小,首屏加载速度得到质的提升。


2. 应用外壳 (App Shell) 与骨架屏 (Skeleton Screens)

这个策略关注于改善用户的感知性能

  • 问题所在:即使使用了代码分割,加载第一个页面的 JS 和数据也需要时间。在这期间显示一个完全空白的页面会给用户带来“卡顿”和“无响应”的感觉。

  • 解决方案

    • 应用外壳 (App Shell):立即渲染一个应用的“框架”,例如页头、导航栏、侧边栏等静态部分。这些内容可以硬编码在 index.html 里,或者用非常小的 JS 快速生成。这样用户会立刻看到一个应用的轮廓,而不是白屏。
    • 骨架屏 (Skeleton Screen):在内容区域,用一些灰色的占位符模拟将要加载出来的内容的布局。你在看 YouTube 或知乎时,经常会先看到文章或视频卡片的灰色轮廓,这就是骨架屏。它让用户知道“内容正在路上”,极大地缓解了等待的焦虑。

实现:骨架屏通常作为 Suspensefallback 或在手动请求数据时的 loading 状态下显示。


3. 资源预加载 (Resource Hints: preload & prefetch)

这个策略是“预测未来”,让浏览器提前下载可能需要的资源。

  • 问题所在:当用户从主页导航到“关于”页面时,浏览器才开始下载“关于”页面的 JS,导航会有延迟。

  • 解决方案:使用 <link> 标签告诉浏览器下一步可能需要什么。

    • <link rel="preload">: 告诉浏览器立即以高优先级下载一个资源,因为它当前页面很快就会用到。例如,预加载一个在屏幕下方、但很重要的图片或字体文件。
    • <link rel="prefetch">: 告诉浏览器在空闲时以下优先级下载一个资源,因为它在未来导航中可能会用到。例如,当用户把鼠标悬停在“登录”按钮上时,你可以动态地 prefetch 登录页面的 JS 代码包。

许多现代框架(如 Next.js)和构建工具的插件会自动帮你处理 prefetch。


4. 其他重要的优化

  • 打包优化 (Build Optimization)

    • Tree Shaking: 确保你的打包工具(如 Vite 或 Webpack)配置正确,它会自动移除你代码中没有被使用的部分。
    • 压缩 (Minification/Compression): 压缩 JS 和 CSS 代码(移除空格、缩短变量名),并在服务器上启用 Gzip 或 Brotli 压缩来减少传输体积。Vite 在生产构建时会自动完成这些。
  • 图片和字体优化

    • 使用现代图片格式 (如 WebP, AVIF)。
    • 对图片进行懒加载 (<img loading="lazy">)。
    • 优化字体加载策略,避免字体文件阻塞页面渲染。

总结:CSR 优化策略

策略核心作用实现方式对用户的效果
代码分割大幅减少首屏必须下载的 JS 体积。React.lazy()<Suspense>首屏加载时间从几秒缩短到毫秒级,白屏时间大幅减少。
骨架屏/App Shell改善用户感知性能,消除白屏焦虑。作为 Suspensefallback 或手动实现用户立刻看到页面结构和加载占位符,感觉应用响应迅速。
资源预加载加速后续页面的导航和资源显示。<link rel="prefetch/preload">点击链接后,页面切换几乎是瞬时的,因为资源已经下载好了。
打包与资源优化全面减小所有需要下载的资源的总大小。Vite/Webpack 配置、图片/字体优化网站整体加载速度更快,流量消耗更少。

通过综合运用这些技术,一个纯客户端渲染(CSR)的应用完全可以将首屏性能优化到一个非常优秀的水平,足以媲美许多 SSR 应用的体验,尤其是在后续页面导航的流畅度上。