如何将 React 应用加载时间缩短 70%
使用代码分割来减少 React 应用程序初始加载时间的步骤。
我们使用 React 构建大型应用。构建这些应用时,我们面临的主要问题是应用性能。随着应用规模越来越大,性能可能会下降。尤其是应用的初始加载时间会受到更大的影响。应用的初始加载速度必须足够快,不能让用户看到几秒钟的空白屏幕。因为加载时间过长会给用户留下不好的印象。
导致此问题的主要原因是将过多的组件添加到单个 bundle 文件,导致加载该文件可能需要更多时间。为了避免此类问题,我们需要以优化的方式构建组件。为了解决这个问题,React 本身提供了一个原生解决方案,即代码拆分和延迟加载。这可以将 bundle 文件拆分成更小的尺寸。
引入代码拆分的最佳位置是在路由中。基于路由的代码拆分解决了一半的问题。但大多数应用只利用了代码拆分 50% 的优势。
使用代码拆分时,我们是否正确地构建了组件?我们可以通过一些代码示例来了解原因以及如何修复它。为此,我们将使用一个包含一些 UI 组件的 React 应用示例。
在下面的截图中,我们可以看到一个仪表板组件,它有多个选项卡。每个选项卡又有多个组件。
Dashboard 组件使用基于路由的代码拆分,如下面的代码所示。
const Dashboard = lazy(() => import('components/Dashboard')); | |
const Settings = lazy(() => import('components/Settings')); | |
const Configurations = lazy(() => import('components/Configurations')); | |
function App() { | |
return ( | |
<Router> | |
<Layout> | |
<SideBar/> | |
<Layout> | |
<AppHeader/> | |
<Content style={{padding: '20px'}}> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Switch> | |
<Route path="/dashboard"> | |
<Dashboard/> | |
</Route> | |
<Route path="/settings"> | |
<Settings/> | |
</Route> | |
<Route path="/configuration"> | |
<Configurations/> | |
</Route> | |
</Switch> | |
</Suspense> | |
</Content> | |
</Layout> | |
</Layout> | |
</Router> | |
); | |
} |
const Dashboard = lazy(() => import('components/Dashboard')); | |
const Settings = lazy(() => import('components/Settings')); | |
const Configurations = lazy(() => import('components/Configurations')); | |
function App() { | |
return ( | |
<Router> | |
<Layout> | |
<SideBar/> | |
<Layout> | |
<AppHeader/> | |
<Content style={{padding: '20px'}}> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Switch> | |
<Route path="/dashboard"> | |
<Dashboard/> | |
</Route> | |
<Route path="/settings"> | |
<Settings/> | |
</Route> | |
<Route path="/configuration"> | |
<Configurations/> | |
</Route> | |
</Switch> | |
</Suspense> | |
</Content> | |
</Layout> | |
</Layout> | |
</Router> | |
); | |
} |
仪表板组件包含一些子组件,如销售、利润、图表、图块和趋势,如以下代码所示
function Dashboard() { | |
return ( | |
<Tabs defaultActiveKey="1"> | |
<TabPane tab="Sales" key="1"> | |
<Sales/> | |
</TabPane> | |
<TabPane tab="Profit" key="2"> | |
<Profit/> | |
</TabPane> | |
<TabPane tab="Chart" key="3"> | |
<Chart/> | |
</TabPane> | |
<TabPane tab="Tiles" key="4"> | |
<Tiles/> | |
</TabPane> | |
<TabPane tab="Trends" key="5"> | |
<Trends/> | |
</TabPane> | |
</Tabs> | |
); | |
} |
function Dashboard() { | |
return ( | |
<Tabs defaultActiveKey="1"> | |
<TabPane tab="Sales" key="1"> | |
<Sales/> | |
</TabPane> | |
<TabPane tab="Profit" key="2"> | |
<Profit/> | |
</TabPane> | |
<TabPane tab="Chart" key="3"> | |
<Chart/> | |
</TabPane> | |
<TabPane tab="Tiles" key="4"> | |
<Tiles/> | |
</TabPane> | |
<TabPane tab="Trends" key="5"> | |
<Trends/> | |
</TabPane> | |
</Tabs> | |
); | |
} |
我们已将代码拆分成多个路由。因此,当应用程序捆绑时,我们会为每个路由获得一个单独的构建文件,如下所示
从上图可以看出,大小为405.1 KB的文件是仪表板组件,其他文件分别是标题、侧边栏、其他组件和 CSS。
我已经将应用程序托管在Netlify中以测试其性能。由于我们在本地测试应用程序,因此无法发现任何差异。当我使用GTmetrix测试托管应用程序时,仪表板屏幕加载耗时2.9 秒,请查看下图逐帧加载的情况。
仪表板组件是此应用程序的初始页面,因此当我们点击应用程序 URL 时,405.1KB文件将与标题和侧边栏一起加载。
最初,用户只会查看“销售”选项卡,但我们的示例应用仪表板组件包含多个选项卡。因此,浏览器还会下载其他选项卡的代码,从而延迟用户的首次绘制。为了减少初始加载时间,我们需要对仪表板组件进行如下更改:
const Sales = lazy(() => import('components/Sales')); | |
const Profit = lazy(() => import('components/Profit')); | |
const Chart = lazy(() => import('components/Chart')); | |
const Tiles = lazy(() => import('components/Tiles')); | |
const Trends = lazy(() => import('components/Trends')); | |
const { TabPane } = Tabs; | |
function Dashboard() { | |
return ( | |
<Tabs defaultActiveKey="1"> | |
<TabPane tab="Sales" key="1"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Sales/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Profit" key="2"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Profit/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Chart" key="3"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Chart/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Tiles" key="4"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Tiles/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Trends" key="5"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Trends/> | |
</Suspense> | |
</TabPane> | |
</Tabs> | |
); | |
} |
const Sales = lazy(() => import('components/Sales')); | |
const Profit = lazy(() => import('components/Profit')); | |
const Chart = lazy(() => import('components/Chart')); | |
const Tiles = lazy(() => import('components/Tiles')); | |
const Trends = lazy(() => import('components/Trends')); | |
const { TabPane } = Tabs; | |
function Dashboard() { | |
return ( | |
<Tabs defaultActiveKey="1"> | |
<TabPane tab="Sales" key="1"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Sales/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Profit" key="2"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Profit/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Chart" key="3"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Chart/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Tiles" key="4"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Tiles/> | |
</Suspense> | |
</TabPane> | |
<TabPane tab="Trends" key="5"> | |
<Suspense fallback={<div>Loading...</div>}> | |
<Trends/> | |
</Suspense> | |
</TabPane> | |
</Tabs> | |
); | |
} |
在这里,我使用延迟加载导入了每个选项卡组件,并使用悬念包装了组件。
在这里,为了更好地理解,我添加了多个悬念,但您可以对所有组件使用单个悬念。
我没有对路由级别的代码拆分做任何改动。在构建应用时,由于我们对仪表板组件中的每个选项卡进行了延迟加载,因此会添加一些额外的文件。请查看下图了解构建文件拆分的情况。
现在,让我们再次使用 GTmetrix 测试应用,并进行上述更改。查看下图中的应用性能。
如您所见,现在我们的仪表板组件仅用1 秒就加载完毕,因为现在只加载了“销售”标签页的代码。通过一些改进,我们缩短了近2 秒。让我们在下图中比较基于路由和基于路由、组件的代码拆分。
如您所见,应用程序的初始加载速度有了显著提升。现在,我们通过在仪表盘组件中有效使用代码拆分,进行了一些调整,将 React 应用程序的初始加载时间缩短了 70%。
参考
结论
以优化的方式构建组件并有效地使用 React API 将提高大型应用程序的性能。
感谢您的阅读。
在Twitter上获取更多更新。
你可以给我买杯咖啡来支持我 ☕
电子书
使用 ChatGPT 调试 ReactJS 问题:50 个基本技巧和示例
更多博客
- 使用 Next.js、NextAuth 和 TailwindCSS 的 Twitter 关注者追踪器
- 不要优化你的 React 应用,而是使用 Preact
- 使用 Next.js、Tailwind 和 Vercel 构建支持暗黑模式的投资组合
- React 中不再导入 ../../../
- 10 个 React 包,包含 1K 个 UI 组件
- Redux Toolkit - 编写 Redux 的标准方法
- 5 个软件包可在开发过程中优化和加速您的 React 应用
- 如何在 React 中以优化和可扩展的方式使用 Axios
- 15 个自定义 Hooks 让你的 React 组件更轻量
- 免费托管 React 应用的 10 种方法
- 如何在单页应用程序中保护 JWT