Глубокое понимание React: ленивая загрузка (lazy) принцип реализации

React.js

содержание

  • разделение кода
  • Ленивая загрузка в React
    • принцип импорта()
    • Принцип React.lazy
    • Принцип ожидания
  • Ссылаться на

1. Разделение кода

(1) Почему разделение кода?

Теперь интерфейсные проекты в основном используют технологию упаковки, такую ​​​​как Webpack.После упаковки кода логики JS будет создан файл bundle.js.По мере того, как мы обращаемся ко все большему количеству сторонних библиотек или код бизнес-логики становится все более и более сложным, упакуйте его соответствующим образом.Размер файла bundle.js будет становиться все больше и больше, потому что страница будет отображаться после того, как ресурс должен быть загружен в первую очередь, что серьезно повлияет на загрузку первого экрана страницы.

Чтобы решить эту проблему и избежать крупномасштабных пакетов кода, мы можем разделить пакеты кода с помощью технических средств, а также создать несколько пакетов и динамически загружать их во время выполнения. Сборщики, такие как Webpack, Browserify и т. д., теперь поддерживают методы разделения кода.


(2) Когда следует рассматривать разделение кода?

Вот сценарий, с которым можно столкнуться при обычной разработке. Например, относительно большая сторонняя библиотека или подключаемый модуль (например, JS-версия библиотеки предварительного просмотра PDF) работает только на странице, которая не является домашней страницей одностраничное приложение (SPA), в этом случае можно рассмотреть сегментацию кода для увеличения скорости загрузки первого экрана.


2. Ленивая загрузка React

Образец кода:

import React, { Suspense } from 'react';

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

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

В приведенном выше коде с помощьюimport(),React.lazyа такжеSuspenseВместе реализуется ленивая загрузка React, то есть мы часто говорим динамическая загрузка во время выполнения, то есть файл компонента OtherComponent разделяется и упаковывается в новый файл пакета, и будет использоваться только при рендеринге компонента OtherComponent. Скачать на локалку.

Так как же именно разделение кода и динамическая загрузка реализованы в приведенном выше примере? Давайте рассмотрим, как это работает.


принцип импорта()

import()Функция — это реализация спецификации динамически загружаемого модуля, предложенная TS39, и ее возврат — обещание. В среде размещения браузераimport()Эталонная реализация выглядит следующим образом:

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 = () => {
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };

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

    document.documentElement.appendChild(script);
  });
}

Когда Webpack разрешает этоimport()Когда используется синтаксис, разделение кода происходит автоматически.


Принцип React.lazy

Следующий исходный код React основан на версии 16.8.0.


Реализация исходного кода React.lazy выглядит следующим образом:

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;
}

Вы можете видеть, что он возвращает объект LazyComponent.


И для разрешения объекта LazyComponent:

...
case LazyComponent: {
  const elementType = workInProgress.elementType;
  return mountLazyComponent(
    current,
    workInProgress,
    elementType,
    updateExpirationTime,
    renderExpirationTime,
  );
}
...
function mountLazyComponent(
  _current,
  workInProgress,
  elementType,
  updateExpirationTime,
  renderExpirationTime,
) { 
  ...
  let Component = readLazyComponentType(elementType);
  ...
}
// 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 首次被渲染
      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;
      throw thenable;
    }
  }
}

Примечание. Если функция readLazyComponentType обрабатывает один и тот же lazyComponent несколько раз, она может войти в состояние Pending, Rejected и другие.

Как видно из приведенного выше кода, для начальногоReact.lazy()Возвращенный объект LazyComponent, его _status по умолчанию равен -1, поэтомупервый рендер, он войдет в логику по умолчанию в функцию readLazyComponentType, и здесь он будет выполняться асинхронно.import(url)Операция, поскольку она не ждет, она затем проверит, разрешен ли модуль, и если он был разрешен (уже загружен), он вернется напрямую.moduleObject.default(экспорт по умолчанию для динамически загружаемых модулей), в противном случае thenable будет переброшен на верхний уровень через throw.

Зачем бросать? Это включает в себяSuspenseПринцип работы мы продолжим разбирать ниже.


Принцип ожидания

Так как в React много логики кода для захвата исключений и их обработки, исходный код здесь не выложен.Если вам интересно, вы можете зайти и посмотретьthrowExceptionЛогика в , которая включает в себя, как обрабатывать перехваченные исключения. Кратко опишите процесс обработки. После того, как React перехватит исключение, он определит, является ли исключение затемабельным. Если это так, он найдет SuspenseComponent. Если затемнимый объект находится в состоянии ожидания, он отобразит его дочерние элементы как резервные значения. затем разрешено, затем подкомпоненты SuspenseComponent повторно визуализируются один раз.


Для простоты понимания мы также можем использовать componentDidCatch для реализации нашего собственного компонента Suspense следующим образом:

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

  componentDidCatch(err) {
    // 判断 err 是否是 thenable
    if (err !== null && typeof err === 'object' && typeof err.then === 'function') {
      this.setState({ promise: err }, () => {
        err.then(() => {
          this.setState({
            promise: null
          })
        })
      })
    }
  }

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

резюме

До сих пор мы анализировали принцип ленивой загрузки React. Проще говоря, React используетReact.lazyа такжеimport()Реализована динамическая загрузка во время рендеринга и используетсяSuspenseдля обработки того, как страница должна отображаться, когда ресурс загружается асинхронно.


3. Ссылка

Разделение кода — React

Динамический импорт - MDN - Mozilla

proposal-dynamic-import

Принцип реализации React Lazy