Компонент повторно использовать эти вещи - нагрузки на реагирование нагрузки

внешний интерфейс Google React.js Redux
Компонент повторно использовать эти вещи - нагрузки на реагирование нагрузки

Компонентизация — очень важная концепция в современной области фронтенд-разработки. Известные интерфейсные библиотеки классов, такие как React, Vue и т. д., очень уважают эту концепцию. Действительно, преимущества повторного использования компонентов и модульности имеют неотъемлемые преимущества для сложных требований сцены. Компоненты подобны блокам Lego и строительным блокам, и небольшое сращивание составляет наше приложение.

В то же время концепция ленивой загрузки/загрузки по требованию имеет решающее значение. Он предлагает новые идеи по оптимизации производительности страницы и улучшению взаимодействия с пользователем. При необходимости мы запрашиваем меньше ресурсов, анализируем меньше скриптов и выполняем меньше контента, и тем лучше результаты.

В этой статье будет проанализировано время отложенной загрузки, методы повторного использования компонентов и примеры кода. Приятного чтения!

Анализ дизайна сцены загрузки по запросу

Типичная страница ниже:

页面构成

Он содержит следующие блоки:

  • заголовок заголовка;
  • область отображения изображения;
  • область отображения карты;
  • Нижний колонтитул страницы.

Соответствующий пример кода:

const Page = () => {
  <div>
    <Header />
    <Gallery />
    <Map />
    <Footer />
  </div>
};

Когда пользователь посещает, если страница не прокручивается, видна только область заголовка. Но во многих сценариях мы загружаем все сценарии JavaScript, ресурсы CSS и другие ресурсы для отображения полной страницы. Это, очевидно, не нужно, потребляет больше трафика и замедляет время загрузки страницы. По этой причине в истории внешнего интерфейса было проведено много исследований ленивой загрузки, и в ответ на эту ситуацию появилось много проектов с открытым исходным кодом крупных компаний: например, Yahoo'sYUI Loader, FacebookHaste, Bootloader and PrimerЖдать. По сей день код, реализующий скрипты с отложенной загрузкой, по-прежнему полезен для изучения. Здесь нет дальнейшего расширения.

Как показано на рисунке ниже, при нормальной логике на уровне покрытия кода мы видим, что 1,1 МБ/1,5 МБ (76%) кода не применяется.

代码覆盖率

Кроме того, не все ресурсы нужно лениво загружать, нам необходимо учитывать следующие моменты на уровне проектирования:

  • Не загружайте содержимое верхней части страницы по запросу.这很好理解,首屏时间至关重要,用户能够越早看到越好。那么如何定义首屏内容?这需要结合用户终端,站点布局来考虑;
  • .我们应该避免给用户呈现空白内容,因此预先懒加载,提前执行脚本对于用户体验的提升非常明显。比如下图,在图片出现在屏幕 100px 时,提前进行图片请求和渲染;

预先加载

  • .这里面涉及到内容较多,需要开发者了解搜索引擎爬虫机制。以 Googlebot 为例,它支持 IntersectionObserver,但是也仅仅对视口里内容起作用。这里不再详细展开,感兴趣的读者可以通过Тестовая страницатак же какИсходный код страницыи поэкспериментировали с инструментами Google для веб-мастеров: Просмотреть как Google.

Технология повторного использования компонентов React

Когда дело доходит до повторного использования компонентов, большинство разработчиков должны быть знакомы с компонентами более высокого порядка. Такие компоненты принимают другие компоненты, выполняют функциональные улучшения и, наконец, возвращают компонент для использования. Подключение React-redux — типичное применение каррирования, пример кода:

const MyComponent = props => (
  <div>
    {props.id} - {props.name}
  </div>
);
// ...
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)( MyComponent );

Точно так же чаще используются технологии «Функция как дочерний компонент» или «Обратный вызов рендеринга». Широко используются многие библиотеки React, такие как react-media и unstated. Возьмем, к примеру, реагирующие СМИ:

const MyComponent = () => (
  <Media query="(max-width: 599px)">
    {matches =>
      matches ? (
        <p>The document is less than 600px wide.</p>
      ) : ( <p>The document is at least 600px wide.</p>
      )
    }
  </Media>
);

Компонент Media вызовет своих дочерних элементов для рендеринга.Основная логика такова:

class Media extends React.Component {
	...
	render() {
		React.Children.only(children)
	}
}

Таким образом, подкомпонентам не нужно воспринимать логику медиазапроса, а затем выполнять повторное использование.

Кроме того, существует множество техник повторного использования компонентов, таких как реквизиты рендеринга и т. д., которые здесь анализироваться не будут.Заинтересованные читатели могут найти соответствующий контент в моей новой книге.

Кодовый бой

Давайте запачкаем руки и реализуем колесо загрузки по требованию. Во-первых, нам нужно разработать компонент Observer, который будет определять, виден ли целевой блок в области просмотра. Чтобы упростить ненужную логику, мы используемIntersection Observer API, который асинхронно отслеживает визуальное состояние целевого элемента. Его совместимость может относиться кздесь.

class Observer extends Component {
  constructor() {
    super();
    this.state = { isVisible: false };
    this.io = null;
    this.container = null;
  }
  componentDidMount() {
    this.io = new IntersectionObserver([entry] => {
      this.setState({ isVisible: entry.isIntersecting });
    }, {});
    this.io.observe(this.container);
  }
  componentWillUnmount() {
    if (this.io) {
      this.io.disconnect();
    }
  }
  render() {
    return (
      // 这里也可以使用 findDOMNode 实现,但是不建议
      <div
        ref={div => {
          this.container = div;
        }}
      >
        {Array.isArray(this.props.children)
          ? this.props.children.map(child => child(this.state.isVisible))
          : this.props.children(this.state.isVisible)}
      </div>
    );
  }
}

Как и выше, компонент имеет состояние isVisible, указывающее, виден ли целевой элемент. this.io представляет текущий экземпляр IntersectionObserver, this.container представляет текущий элемент наблюдения, который завершает получение целевого элемента через ref.

В методе componentDidMount переключаем состояние this.setState.isVisible, в методе componentWillUnmount выполняем сборку мусора.

Очевидно, что этот метод повторного использования является упомянутой выше функцией в качестве дочернего компонента.

Обратите внимание, что для приведенной выше базовой реализации мы совершенно свободны в индивидуальной персонализации. IntersectionObserver поддерживает параметры полей или порогов. Мы можем инициализировать элемент конфигурации в конструкторе и обновить его в функции жизненного цикла componentWillReceiveProps.

Таким образом, для содержимого предыдущей страницы мы можем лениво загрузить компонент «Галерея» и компонент «Карта»:

const Page = () => {
    <div>
        <Header />
        <Observer>
          {isVisible => <Gallery isVisible />}
        </Observer>
        <Observer>
          {isVisible => <Map isVisible />}
        </Observer>
        <Footer />
    </div>
}

Проходим состояние isVisible. Соответствующий потребительский компонент может выборочно отображаться в соответствии с isVisible. Реализация:

class Map extends Component {
  constructor() {
    super();
    this.state = { initialized: false };
    this.map = null;
  }
initializeMap() {
    this.setState({ initialized: true });
    // 加载第三方 Google map
    loadScript("https://maps.google.com/maps/api/js?key=<your_key>", () => {
      const latlng = new google.maps.LatLng(38.34, -0.48);
      const myOptions = { zoom: 15, center: latlng };
      const map = new google.maps.Map(this.map, myOptions);
    });
  }
componentDidMount() {
    if (this.props.isVisible) {
      this.initializeMap();
    }
  }
componentWillReceiveProps(nextProps) {
    if (!this.state.initialized && nextProps.isVisible) {
      this.initializeMap();
    }
  }
render() {
    return (
      <div
        ref={div => {
          this.map = div;
        }}
      />
    );
  }
}

Только когда компонент карты соответствующую контейнер появляется в просмотре, мы идем, чтобы загрузить сторонние ресурсы.

Аналогично для компонента «Галерея»:

class Gallery extends Component {
  constructor() {
    super();
    this.state = { hasBeenVisible: false };
  }
  componentDidMount() {
    if (this.props.isVisible) {
      this.setState({ hasBeenVisible: true });
    }
  }
  componentWillReceiveProps(nextProps) {
    if (!this.state.hasBeenVisible && nextProps.isVisible) {
      this.setState({ hasBeenVisible: true });
    }
  }
  render() {
    return (
      <div>
        <h1>Some pictures</h1>
        Picture 1
        {this.state.hasBeenVisible ? (
          <img src="http://example.com/image01.jpg" width="300" height="300" />
        ) : (
          <div className="placeholder" />
        )}
        Picture 2
        {this.state.hasBeenVisible ? (
          <img src="http://example.com/image02.jpg" width="300" height="300" />
        ) : (
          <div className="placeholder" />
        )}
      </div>
    );
  }
}

Это также может быть реализовано с использованием компонентов без природы / функциональные компоненты:

const Gallery = ({ isVisible }) => (
  <div>
    <h1>Some pictures</h1>
    Picture 1
    {isVisible ? (
      <img src="http://example.com/image01.jpg" width="300" height="300" />
    ) : (
      <div className="placeholder" />
    )}
    Picture 2
    {isVisible ? (
      <img src="http://example.com/image02.jpg" width="300" height="300" />
    ) : (
      <div className="placeholder" />
    )}
  </div>
);

Это, без сомнения, будет более лаконичным. Но когда элементы вне просмотра просмотра соответствующая картина не будет продолжать показывать, но воспроизвести заполнитель.

Если нам нужно, чтобы лениво загружаемый контент записывался только один раз за жизненный цикл страницы, мы можем установить параметр hasBeenVisible:

const Page = () => {
  ...
  <Observer>
    {(isVisible, hasBeenVisible) =>
      <Gallery hasBeenVisible /> // Gallery can be now stateless
    }
  </Observer>
  ...
}

class ObserverOnce extends Component {
  constructor() {
    super();
    this.state = { hasBeenVisible: false };
    this.io = null;
    this.container = null;
  }
  componentDidMount() {
    this.io = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          this.setState({ hasBeenVisible: true });
          this.io.disconnect();
        }
      });
    }, {});
    this.io.observe(this.container);
  }
  componentWillUnmount() {
    if (this.io) {
      this.io.disconnect();
    }
  }
  render() {
    return (
      <div
        ref={div => {
          this.container = div;
        }}
      >
        {Array.isArray(this.props.children)
          ? this.props.children.map(child => child(this.state.hasBeenVisible))
          : this.props.children(this.state.hasBeenVisible)}
      </div>
    );
  }
}

больше сцен

Выше мы использовали компонент Observer для загрузки ресурсов. Включает сторонний контент и изображения Google Map. Мы также можем выполнить требование «показывать анимацию элемента только тогда, когда компонент появляется в окне просмотра».

Следуя веб-сайту React Alicante, мы реализуем аналогичную потребность в выполнении анимации по запросу. конкретный видимыйкодовый адрес.

IntersectionObserver polyfilling

Ранее упоминалась совместимость IntersectionObserver API, что естественно не может обойти тему полифилла.

Вариантом обработки совместимости является «прогрессивное улучшение», то есть только загрузки по запросу в поддерживаемых сценах, в противном случае всегда устанавливайте статус isvisible: TRUE:

class Observer extends Component {
  constructor() {
    super();
    this.state = { isVisible: !(window.IntersectionObserver) };
    this.io = null;
    this.container = null;
  }
  componentDidMount() {
    if (window.IntersectionObserver) {
      this.io = new IntersectionObserver(entries => {
        ...
      }
    }
  }
}

Это, очевидно, не может достичь цели по требованию, я рекомендую w3cIntersectionObserver polyfill:

class Observer extends Component {
  ...
  componentDidMount() {
    (window.IntersectionObserver
      ? Promise.resolve()
      : import('intersection-observer')
    ).then(() => {
      this.io = new window.IntersectionObserver(entries => {
        entries.forEach(entry => {
          this.setState({ isVisible: entry.isIntersecting });
        });
      }, {});
      this.io.observe(this.container);
    });
  }
  ...
}

Когда браузер не поддерживает IntersectionObserver, мы динамически импортируем полифилл, для чего требуется поддержка динамического импорта.Это отдельная тема, и здесь она не будет раскрыта.

В качестве последнего теста в неподдерживаемом браузере Safari мы видим временную шкалу сети следующим образом:

时间线

Суммировать

В этой статье рассказывается о повторном использовании компонентов и загрузке по требованию (отложенной загрузке) содержимого реализации. Для получения дополнительных знаний вы можете следить за новой книгой автора. В то же время эта статья была взята изJosé M. PérezУлучшение производительности вашего сайта с помощью отложенной загрузки и разделения кода с некоторыми изменениями.

коммерческое время:Если вы интересуетесь фронтенд-разработкой, особенно стеком React: возможно, в моей новой книге есть что-то, что вы хотели бы увидеть. Следите за авторомLucas HC, после выхода новой книги будет розыгрыш.

Happy Coding!

ПС: авторРепозиторий на гитхабе а также Знай ссылку на вопрос и ответПриветствуются все формы общения.

Идея промежуточного программного обеспечения React Redux соответствует вдохновению Web Worker (с демонстрацией)

Из обсуждения обещания setState познакомьтесь с дизайнерским мышлением команды React.

Изучите «лучшие практики» для написания компонентов React на примере

React компонентный дизайн и декомпозиционное мышление

Привязки реагирования от этого см. JS Language Development и Framework

Сделать мобильную веб-версию Uber недостаточно

React+Redux создает одностраничное приложение «NEWS EARLY», проект понимает истинное значение стека передовых технологий**