This article has been in stock for a long time…
React16.6 was released in October 2018, which brought a number of new features and enhanced functionality to React. The two most notable features are react.suspense and react.lazy. These two features take React code splitting and lazy loading to a whole new level. What you can do with these two features is load the component’s files only when you really need them.
This article focuses on how I use React.suspense and React.lazy in my project and the benefits this feature brings to us React developers.
Why use code splitting
With the continuous development of front-end technology, ES6 module, Babel conversion, WebPack packaging and other new technologies, front-end applications can now be completed in a modular way, easy to maintain. Typically, we package all the modules into a file that is loaded when a web page is requested to display the entire application. However, as web features continue to expand, this leads to slow loading of web pages, slow interaction, and poor user experience. The main reason for this problem is that when the page loads, we load all the code at once, both the current code and the code that will be used later. But users didn’t use all the features the first time they came in, so code-splitting — code splitting — came into being.
Webpack, for example, provides the ability to split code. Code splitting is defined in Webpack as follows:
Code splitting is one of the most compelling features of webpack. This feature allows you to split your code into various bundles which can then be loaded on demand or in parallel.
This means that code can be split into bundles and loaded on demand or in parallel. Therefore, in order to improve the performance of the application, we can try how to reasonably split the code and delay loading.
How to split the code?
1. Dynamic loading
The ES6 standard introduced imports to make it easier to statically load modules. Forms such as:
import xxx from xxx.jsCopy the code
While import is useful for loading modules, statically loading modules is somewhat limited to asynchronous loading. However, the import() syntax for dynamically loading modules is currently in the proposal stage, and WebPack has introduced it and uses it. Import () provides a Promise based API, so the return value of import() is a Promise object with a fulfilled or rejected state. Forms such as:
import(/* webpackChunkName: 'module'* /"module")
.then(() => {
//todo
})
.catch(_ => console.log('It is an error'))
Copy the code
When WebPack recognizes dynamically loaded import syntax at compile time, WebPack creates a separate bundle for the currently dynamically loaded module. If you’re using the official create-react-app scaffolding or the React server rendering framework Next-.js, you can use the dynamic import syntax directly. If your scaffolding is a webpack you configured yourself, then you need to follow the official guide to setup, go to [1].
2. Dynamically load the React component
One of the most popular methods is to lazily load the React component using the React-loadable[2] library. It uses the import() syntax and uses the Promise syntax to load the React component. Meanwhile, React-loadable supports server-side rendering of React. In general, we implement components as follows:
import LazyComponet from 'LazyComponent';
export default function DemoComponent() {
return (
<div>
<p>demo component</p>
<AComponent />
</div>
)
}
Copy the code
In the example above, we assume that LazyComponet is not displayed when DemoComponent is rendered. But because we import LazyComponet using the import syntax, the code for LazyComponet and DemoComponent will be packaged in the same bundle at compile time. But that’s not what we want. So we can lazily load LazyComponet by using react-loadable and bundle LazyComponet into a separate bundle. Take a look at the example provided on the website:
import Loadable from 'react-loadable';
import Loading from './my-loading-component';
const LoadableComponent = Loadable({
loader: () => import('./my-component'),
loading: Loading,
});
export default class App extends React.Component {
render() {
return<LoadableComponent/>; }}Copy the code
As we can see from the example, react-loadable uses the dynamic import() method and assigns the imported components to the Loader property. Also, react-loadable provides a loading property to set the component that will be displayed when it is loaded.
Use of react. lazy and react. suspense
React.lazy and Suspense is not yet available for server-side rendering. If you want to do code-splitting in a server rendered app, we recommend Loadable Components. It has a nice guide for bundle splitting with server-side rendering.
Before using React, it’s important to note that React is explicitly supported. React.lazy and Suspense don’t support server-side rendering. Therefore, for those using server rendering, please detour to react-loadable and loadable components[3].
Since I was upgrading the original project, the following contents of this article mainly focus on the work of upgrading the latest version of React for the old project.
1. Update the React code
If your code is Update V16, you can upgrade directly to the latest version, although Update 16.6 already provides lazy and suspense methods. If the migration is before V16, follow the official operations.
2. Identify lazy-loaded components of the original code
Firstly, according to the requirements, components that are not loaded on the first screen are identified as lazy loading components. My project has identified five components that can be lazy loading. The modification method is very simple. The original method of importing components is:
import LazyComponent from ".. /components/LazyComponent ";Copy the code
Is amended as:
const LazyComponent = React.lazy(() =>
import(/* webpackChunkName: 'lazyComponent'* /".. /components/LazyComponent"));Copy the code
Replace the statically referenced component with a call to react.lazy (), passing an anonymous function as an argument to lazy(), and dynamically introducing the lazyComponent into the function. So the browser won’t download the lazyComponent.bundle.js file and its dependencies until we render the component. Where, the webpackChunkName in import is the bundle name defined by us.
What happens when React renders components that depend on code that has not yet been downloaded? < react.suspense /> comes in to help us out. It will render the values passed in by the fallback property until the code is downloaded. So our original code is:
return (<div><MainComponet /><LazyComponent /></div>)Copy the code
Is amended as:
returnIn Suspense </div> < react. Suspense </div>Copy the code
The fallback can be changed to any spinner, so we don’t need to optimize too much this time. React will give you an error if you don’t use React.Suspense, so remember to use React. Lazy with React. At this point we can look at our network request.
As you can see from the figure, the lazyComponet component that we dynamically loaded is individually packaged into a bundle. However, when the first screen loads, the bundle is already loaded into our page, which may not be what we want. We want to load it when we need it. Next, we’ll take control and load the file when we need it.
3. Control loading through variables
Originally, the five lazy-loading components I selected were all shell components, so it was inevitable to set a state to control the display and hide of this component, so we changed the code to:
return(< div > < MainComponet / > {this. State. ShowLazyComponent && (< React. The Suspense fallback = {< div > < / div >} in being loaded > < LazyComponent /> </React.Suspense> )} </div> )Copy the code
At this point we can look at network requests.
As you can see from the figure, the bundle corresponding to our lazy loading component LazyComponent is not loaded during the first screen load. When we click to display the component, the page loads the JS. This serves the purpose of separating and lazily loading our code. So if we do this, does the size of the main bundle decrease? Next, let’s pack up the files and have a look.
4. Pack your files
Files packed before splitting:
Split and packaged files:
The app.js file becomes smaller, with the addition of lazyComponent.js. When there are too many lazy loading components, we can reduce the size of the loading file on the first screen to some extent and improve the rendering speed of the first screen.
Fourth, verify the effectiveness of optimization
Compare Puppeteer with the Performance API
To verify the effectiveness of the optimization I made earlier, I ran a set of comparative experiments. The experiment content is to use Puppeteer to access the pre-optimization and post-optimization pages for 1000 times, and use Performance API to calculate the average value of five data during these 1000 times. The experimental results are shown in the figure below:
-
A is the average request time
-
B is the average dom tree parsing time
-
C is the average time between request completion and DOM loading
-
D indicates the average time from the start of a request to the end of domContentLoadedEvent
-
E is the average time from the request start to load
The line chart cannot accurately show the data, so the attached table data is as follows:
category | The optimized | Before optimization |
---|---|---|
A(Request Average Request Time) | 7.013 | 7.042 |
B(Average dom tree parsing time) | 30.284 | 32.597 |
C(Average time between request completion and DOM loading) | 552.861 | 582.01 |
D(Average time between request start and end of domContentLoadedEvent) | 569.137 | 589.07 |
E(Average time from request start to Load) | 1055.597 | 1126.941 |
It can be seen from the data that the request time before and after optimization has no impact, but the total load time is significantly shortened and immediately enters the 1000ms mark, indicating that the loading speed of the first screen is significantly improved after optimization.
Note: In the process of puppeteer running 1000 times, network fluctuation will occur, resulting in large data of some requests. Therefore, the average value cannot fully reflect the normal request time. But an average of 1000 times is enough to compare request times before and after optimization.
2. Compare Chorme Performance parameters
Because the Performance API provides limited parameters, I got the parameters for a single page request from Chrome’s Performance Summary. Since it is a single data, we do not make a detailed comparison. Listed here only to show which parts of the browser render time improved before and after optimization.
Before optimization:
After the optimization:
-
Blue: The Loading time decreases
-
Yellow: Scripting time reduced
-
Purple: Rendering time reduced
-
Green: Painting time is flat
-
Grey: The Other time decreases
-
Idle: The idle time of the browser decreases
In addition, I found from the Network that after optimization, the request time of the main interface is correspondingly advanced because the page parsing is relatively faster than before.
Five, the summary
According to multiple data, the use of react. lazy and react. Suspense speeds up the rendering speed of the first screen to some extent, making our page load faster. In addition, when we introduce a new dependency to add a new feature, we often evaluate the size of the dependency and how much it will affect the bundle. If this feature is rarely used, then you can use react. lazy and react. Suspense to load it on demand without sacrificing the user experience.
Read more
[1] https://webpack.js.org/guides/code-splitting/
[2] https://github.com/jamiebuilds/react-loadable
[3] https://github.com/smooth-code/loadable-components