超简单实用:用Suspence和React.lazy来做代码的切割

1,175 阅读4分钟

开始

浏览器渲染一个页面需要下载并解析编译Html、js、css等文件。如果这些文件很大的话,就会延长首屏加载的时间。而下载的时间往往是其中的大头。

我们第一个想到的是压缩代码,删除注释,CDN加速,或者把大文件分成小文件来并发进行下载,通过这些方法来缩短下载的时间。

还有一个思路:

我们只是想缩短首屏加载的时间,而与首屏渲染相关的代码只是占所有代码的一部分。所以只需要把与首屏渲染相关的代码第一时间给浏览器,其他的代码后面再来传输,这样是不是也可以缩短首屏加载的时间呢?

答案是可以的,关键在于如何实现

这里有一篇关于Suspence的讲解,初学者建议看看:React-解析Suspence-超简单

简单的代码切割

很简单,借助Suspence就可以做到了😁

import { lazy, Suspense, useState } from 'react';
import Title from './Title';
import Title from './Name'
function App() {
  const [flag, setFlag] = useState(false);
  return (
    <div className="App">
      <div onClick={() => { setFlag(true) }}>
        <Title />
      </div>
      {flag ? <Name /> : null }
    </div>
  );
}

上面的代码中,初始化时,Name组件是不渲染的。只有当用户点击Title,Name才会得到渲染。默认情况下,Name代码会和Title代码一起传至浏览器,但Name的代码并不会参与首屏的渲染,这无疑会拖慢渲染的速度。

所以我们可以在用户点击了Title之后,才去下载Name的代码 下面是实现代码

import { lazy, Suspense, useState } from 'react';
import './App.css';
import Title from './Title';

const Name = lazy(() => import('./Name');)

function App() {
    const [flag, setFlag] = useState(false);
    return (
        <div className="App">
            <div onClick={() => { setFlag(true) }}>
                <Title />
            </div>
            <Suspense fallback={<div>loading ...</div>}>
                {flag ? <Name /> : null}
            </Suspense>
        </div>
  );}

我们将Name组件用React.lazy函数导入了进来,并且在使用的时候,用Suspence包裹住。这样就实现了在用户点击Title后,才去下载Name的代码文件。

下面是效果图👇

这是页面初始化时候下载的文件

当我们点击TItle

Name代码文件被另外下载过来了。效果达成😁

这样似乎就完成了代码的分割,而我们只需要动用两个React的API:lazy、Suspence,就能完成这样的效果。如果文件的大小变得不可忽视的时候,该优化的效果是显而易见的

还有一个问题

如果Name组件代码很多,那么用户在点击Title之后,需要较长的时候再能看到Name渲染的效果,这是不好的。

在首屏加载完成之后,到用户点击Title之前,是有一段时间的。而这时间我们可以利用起来,用来下载Name组件代码。这样既不影响首屏的渲染时间,也能让用户更快地看到Name的渲染。

想法是好的,该怎么做呢?

解决代码切割的问题

import { lazy, Suspense, useState } from 'react';
import './App.css';
import Title from './Title';

const temp = import('./Name');
const Name = lazy(() => temp)

function App() {
  const [flag, setFlag] = useState(false);
  return (
    <div className="App">
      <div onClick={() => { setFlag(true) }}>
        <Title />
      </div>
      <Suspense fallback={<div>loading ...</div>}>
        {flag ? <Name /> : null}
      </Suspense>
    </div>
  );
}

我们将import()的返回值用temp变量承接,之后的lazy函数会用到temp来做懒加载。

来看看NetWork中的实际效果

Name组件代码依旧被分割,先下载了APP和TItle组件的代码,后下载了Name的代码。即不需要用户点击Title,也会进行下载

不仅Name组件代码会切割延迟下载,Name组件的依赖也同样如此,这样的结果绝对是令人兴奋的

一般性的用法:按照路由切割代码

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
    <Router>
        <Suspense fallback={<div>Loading...</div>}>
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>
        </Suspense>
    </Router>
);

这是官方文档里面的例子。其中通过路由对不同的组件进行切割,这样可以做到当渲染哪个页面,哪个页面才被下载,而不是全部下载。

这是个好功能,以前见到了不明所以,现在知道了是干嘛的了,相见恨晚呀

据说webpack在打包的过程中,也有代码切割的功能,它是怎么做的呢,这个上面所讲的代码分割有什么不同呢?

总结:

  1. 用Suspence实现代码的切割
  2. 用变量承接import的返回值,来解决代码切割的问题
  3. 一般性的用法,按照路由做代码切割