directory

  • The code segment
  • React lazy loading
    • Import () principle
    • The React. Lazy principle
    • Principle of Suspense
  • reference


1. Code segmentation

(1) Why should code segmentation be carried out?

At present, front-end projects basically adopt packaging technology. For example, Webpack, JS logic code will be packaged into a bundle. JS file. As more and more third-party libraries are referenced or business logic code becomes more and more complex, the volume of bundled bundle. JS file will become larger and larger. Because the page is rendered after the resource is requested, the first screen load of the page is severely affected.

To solve this problem and avoid large code packages, we can split the code packages by technical means, creating multiple packages and loading them dynamically at run time. Packagers like Webpack and Browserify now support code splitting.


(2) When should code splitting be considered?

Here is an example of a common development scenario that may be encountered, such as a relatively large third-party library or plug-in (such as the PDF preview library of the JS version) is only used in a single page application (SPA) page that is not the first page. In this case, code splitting can be considered to increase the loading speed of the first screen.


React lazy loading

Sample code:

import React, { Suspense } from 'react';

const OtherComponent = React.lazy((a)= > import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>} ><OtherComponent />
      </Suspense>
    </div>
  );
}
Copy the code

Use import(), react. lazy, and Suspense to implement lazy loading of React. The OtherComponent file is split into bundles. And it will only be downloaded locally when the OtherComponent is rendering.

How does the above code splitting and dynamic loading work? Let’s explore how this works.


Import () principle

The import() function is a canonical implementation of dynamically loaded modules proposed by TS39 that returns a promise. A reference implementation of import() in the browser hosting environment is as follows:

function import(url) {
  return new Promise((resolve, reject) = > {
    const script = document.createElement("script");
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal}= m; `;

    script.onload = (a)= > {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

    script.onerror = (a)= > {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };

    document.documentElement.appendChild(script);
  });
}
Copy the code

When Webpack parses into the import() syntax, it automatically splits the code.


The React. Lazy principle

The React source code is based on version 16.8.0


React.lazy is implemented as follows:

export function lazy<T.R> (ctor: () = >Thenable<T.R>) :LazyComponent<T> {
  let lazyType = {
    ? typeof: REACT_LAZY_TYPE,
    _ctor: ctor,
    // React uses these fields to store the result.
    _status: - 1._result: null};return lazyType;
}
Copy the code

You can see that it returns a LazyComponent object.


For LazyComponent object parsing:

. case LazyComponent: {const elementType = workInProgress.elementType;
  returnmountLazyComponent( current, workInProgress, elementType, updateExpirationTime, renderExpirationTime, ); }...Copy the code
function mountLazyComponent(_current, workInProgress, elementType, updateExpirationTime, renderExpirationTime,) {... let Component = readLazyComponentType(elementType); . }Copy the code
// Pending = 0, Resolved = 1, Rejected = 2
export function readLazyComponentType<T> (lazyComponent: LazyComponent<T>) :T {
  const status = lazyComponent._status;
  const result = lazyComponent._result;
  switch (status) {
    case Resolved: {
      const Component: T = result;
      return Component;
    }
    case Rejected: {
      const error: mixed = result;
      throw error;
    }
    case Pending: {
      const thenable: Thenable<T, mixed> = result;
      throw thenable;
    }
    default: { // lazyComponent is rendered for the first time
      lazyComponent._status = Pending;
      const ctor = lazyComponent._ctor;
      const thenable = ctor();
      thenable.then(
        moduleObject= > {
          if (lazyComponent._status === Pending) {
            const defaultExport = moduleObject.default;
            lazyComponent._status = Resolved;
            lazyComponent._result = defaultExport;
          }
        },
        error => {
          if(lazyComponent._status === Pending) { lazyComponent._status = Rejected; lazyComponent._result = error; }});// Handle synchronous thenables.
      switch (lazyComponent._status) {
        case Resolved:
          return lazyComponent._result;
        case Rejected:
          throw lazyComponent._result;
      }
      lazyComponent._result = thenable;
      throwthenable; }}}Copy the code

Note: If the readLazyComponentType function processes the same lazyComponent multiple times, it may enter Pending, Rejected, and other cases.

As you can see from the code above, the LazyComponent object originally returned by react.lazy () defaults to -1 with its _status, so the first rendering will enter the logic of default in readLazyComponentType. This is where the import(URL) operation will actually be done asynchronously and because it is not waiting, it will then check whether the module is Resolved and if it is, return moduleObject.default (the default export for dynamically loaded modules). Otherwise thenable will be thrown to the upper layer by throw.

Why throw it? This involves the working principle of Suspense and we will continue to analyze it.


Principle of Suspense

React catches exceptions and handles them with a lot of code logic, so I won’t post the source code here. If you are interested, you can look at the logic in throwException, which includes how to handle the caught exception. React catches the exception and determines whether the exception is a Thenable. If so, it finds the SuspenseComponent. If ThEnable is pending, The SuspenseComponent’s children are rendered as fallback values, and the SuspenseComponent’s children are rerendered once Thenable is resolved.


To make things easier to understand, we can also implement our own Suspense component with componentDidCatch as follows:

class Suspense extends React.Component {
  state = {
    promise: null
  }

  componentDidCatch(err) {
    // Check whether err is thenable
    if(err ! = =null && typeof err === 'object' && typeof err.then === 'function') {
      this.setState({ promise: err }, () => {
        err.then((a)= > {
          this.setState({
            promise: null
          })
        })
      })
    }
  }

  render() {
    const { fallback, children } = this.props
    const { promise } = this.state
    return <>{ promise ? fallback : children }</>}}Copy the code


summary

React is lazy loading. In brief, React uses react.lazy with import() to implement dynamic loading at render time, and Suspense to deal with how pages should appear when resources are loaded asynchronously.


Reference 3.

Code split — React

Dynamic import-mdN-Mozilla

proposal-dynamic-import

React Lazy implementation