preface

This article illustrates the adapter pattern by showing how to dynamically load components.

1. Common routes

import Center from 'page/center';
import Data from 'page/data';

function App(){
    return( <Router> <Switch> <Route exact path="/" render={() => (<Redirect to="/center" />)} /> <Route path="/data" component={Data} /> <Route path="/center" component={Center} /> <Route render={() => <h1 style={{ textAlign: 'center' marginTop: '160 px, color:' rgba (255, 255, 255, 0.7) '}} > page missing < / h1 >} / > < / Switch > < / Router >); }Copy the code

The above are the most common React routers. In simple single-page applications, this is ok. Since the bundled single JS file bundle.js is only around 200K, gzip does not have a significant impact on loading performance. However, as the product goes through multiple iterations, additional pages cause bundle.js to grow in size. This is where optimization becomes necessary.

How to optimize

One of the key concepts used in optimization is load on demand. Load only the components needed for the current page.

For example, if you are currently accessing the/Center page, you only need to load the Center component. There is no need to load the Data component.

The following solutions are currently implemented in the industry:

  • React -router Dynamic routesgetComponentMethod (no longer supported by Router4)
  • Use the React-loadable gadget library
  • Customize high-level components to load on demand

The common point of these schemes is to use the code splitting function of Webpack (Require. Ensure is used in Webpack1, import is used in Webpack2 / Webpack3) to split the code.

Next, I’ll show you how to implement on-demand loading with custom higher-order components.

Customizing higher-order components

3.1 Import method of Webpack

Webpack treats import() as a split point and packages its requested module into a separate chunk. Import () takes the module name as the parameter name and returns a Promise object.

Because import() returns a Promise object, it cannot be used directly with
.

3.2 Encapsulating import in adapter Mode ()

Adapter pattern: Transforms the interface of a class into another interface that the customer wants. The Adapter pattern enables classes to work together that would otherwise not work together due to interface incompatibilities.

In the current scenario, we need to solve the problem of how to load components asynchronously using import() to React for update. And the way to do that is very easy, is to use state. When the component is loaded asynchronously, call the setState method to notify. In this vein, create a higher-level component that encapsulates import() using the adapter pattern.

3.3 Implement asynchronous loading method asyncComponent

import React from 'react';

export const asyncComponent = loadComponent= > (

    class AsyncComponent extends React.Component {
        constructor(... args){super(... args);this.state = {
                Component: null};this.hasLoadedComponent = this.hasLoadedComponent.bind(this);
        }
        componentWillMount() {
            if(this.hasLoadedComponent()){
                return;
            }
    
            loadComponent()
                .then(module= > module.default ? module.default : module)
                .then(Component= > {
                    this.setState({
                        Component
                    });
                })
                .catch(error= > {
                    /*eslint-disable*/
                    console.error('cannot load Component in <AsyncComponent>');
                    /*eslint-enable*/
                    throw error;
                })
        }
        hasLoadedComponent() {
            return this.state.Component ! = =null;
        }
        render(){
            const {
                Component
            } = this.state;

            return (Component) ? <Component {. this.props} / >: null; }});Copy the code
// The usage mode

const Center = asyncComponent((a)= >import(/* webpackChunkName: 'pageCenter' */'page/center'));
Copy the code

As shown in the example, create a new asyncComponent method that receives the Promise object returned by import(). When componentWillMount (server rendering also has this lifecycle method), import() is executed and asynchronously loaded components are set to state, triggering component re-rendering.

3.4 misgivings

  • State.Com ponent initialization
this.state = {
    Component: null};Copy the code

Null is used to determine whether the asynchronous component has been loaded.

  • module.default ? module.default : module

The purpose here is to accommodate both named and default export writing methods.

  • return (Component) ? <Component {... this.props} /> : null;

Null can be replaced with
. It is used as a placeholder when the asynchronous component is not loaded properly. This. Props is transparently passed to the asynchronous component via the AsyncComponent.

3.5 Modify the WebPack build

output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/[name].[chunkhash].js'),
    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')}Copy the code

In the output, add chunkFilename.

Four, summary

The advantage of customizing higher-order components is that you can optimize existing old projects with minimal changes. As in the example above, all you need to do is change the way you import components. For the least amount of money, you can get page performance improvements. In fact, React – Loadable is implemented in the same way, but with a lot of additional feature points.

reference

  1. Implement on-demand loading of react components based on Webpack Code Splitting
  2. React uses the import() implementation of Webpack2 to load components asynchronously

Friends who like my articles can follow me in the following ways:

  • “Star” or “watch” my GitHub blog
  • RSS subscribe to my personal blog:Mr. Wang’s base