Unetway

ReactJS - Оптимизация производительности

Внутри React использует несколько умных методов, чтобы минимизировать количество дорогостоящих операций DOM, необходимых для обновления пользовательского интерфейса. Для многих приложений использование React приведет к быстрому пользовательскому интерфейсу, не выполняя много работы, чтобы специально оптимизировать производительность. Тем не менее, есть несколько способов ускорить ваше приложение React.

Использовать сборку продукции

Если вы проводите бенчмаркинг или испытываете проблемы с производительностью в своих приложениях React, убедитесь, что вы тестируете мини-сборку.

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

Если вы не уверены, правильно ли настроен процесс сборки, вы можете проверить его, установив React Developer Tools для Chrome

Создать приложение React

Если ваш проект построен с помощью приложения Create React , запустите:

npm run build

Это создаст производственную сборку вашего приложения в build/папке вашего проекта. Помните, что это необходимо только перед развертыванием в производство. Для нормальной разработки используйте npm start.

Однофайловые сборки

Мы предлагаем готовые версии React и React DOM в виде отдельных файлов:

<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

Помните, что только файлы Реагирования, оканчивающиеся на .production.min.js, подходят для производства.

Brunch

Для наиболее эффективной сборки сборки Brunch установите uglify-js-brunchплагин:

# If you use npm
npm install --save-dev uglify-js-brunch

# If you use Yarn
yarn add --dev uglify-js-brunch

Затем, чтобы создать сборку сборки, добавьте -p флаг в build команду:

brunch build -p

Помните, что вам нужно всего лишь сделать это для производственных сборок. Вы не должны передавать -p флаг или применять этот плагин в разработке, потому что он скроет полезные предупреждения React и сделает сборки намного медленнее.

Browserify

Для наиболее эффективной сборки сборки Browserify установите несколько плагинов:

# If you use npm
npm install --save-dev envify uglify-js uglifyify 

# If you use Yarn
yarn add --dev envify uglify-js uglifyify

Чтобы создать сборку продукции, убедитесь, что вы добавляете эти преобразования (порядок имеет значение) :

  • envify преобразование обеспечивает правильное окружение сборки устанавливается. Сделайте его global ( -g).
  • uglifyify преобразование удаляет импорт развития. Сделайте его глобальным ( -g).
  • наконец, результирующий пучок передается по uglify-js каналам для извлечения (читайте почему).
browserify ./index.js \
  -g [ envify --NODE_ENV production ] \
  -g uglifyify \
  | uglifyjs --compress --mangle > ./bundle.js

Заметка:

Имя пакета есть uglify-js, но вызывается двоичный файл, который он предоставляет uglifyjs. 
Это не опечатка.

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

Browserify

Для наиболее эффективной сборки сборки Rollup установите несколько плагинов:

# If you use npm
npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify 

# If you use Yarn
yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-uglify 

Чтобы создать сборку, убедитесь, что вы добавляете эти плагины (порядок имеет значение) :

  • replace плагин обеспечивает правильное окружение сборки устанавливается.
  • commonjs плагин обеспечивает поддержку CommonJS в накопительном пакете.
  • uglify плагин сжимает и искажает конечный пучок.
plugins: [
  // ...
  require('rollup-plugin-replace')({
    'process.env.NODE_ENV': JSON.stringify('production')
  }),
  require('rollup-plugin-commonjs')(),
  require('rollup-plugin-uglify')(),
  // ...
]

Помните, что вам нужно всего лишь сделать это для производственных сборок. Вы не должны применять uglify плагин или replaceплагин со 'production' значением в разработке, потому что они будут скрывать полезные предупреждения React и делать сборки намного медленнее.

WebPack

Для создания самой эффективной сборки веб-пакета обязательно включите эти плагины в свою производственную конфигурацию:

new webpack.DefinePlugin({
  'process.env.NODE_ENV': JSON.stringify('production')
}),
new webpack.optimize.UglifyJsPlugin()

Помните, что вам нужно всего лишь сделать это для производственных сборок. Вы не должны применять UglifyJsPlugin или DefinePluginс 'production'значением в развитии , потому что они будут скрывать полезны Реагировать предупреждения, и сделать строит гораздо медленнее.

Компоненты профилирования с вкладкой «Производительность Chrome»

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

Чтобы сделать это в Chrome:

  1. Временно отключите все расширения Chrome, особенно React DevTools . Они могут значительно исказить результаты!
  2. Убедитесь, что вы запускаете приложение в режиме разработки.
  3. Откройте вкладку «Performance Chrome DevTools» и нажмите «Record» .
  4. Выполните действия, которые вы хотите профилировать. Не записывайте более 20 секунд или Chrome может зависать.
  5. Остановите запись.
  6. События React будут сгруппированы под меткой User Timing.

Виртуализировать длинные списки

Если ваше приложение отображает длинные списки данных (сотни или тысячи строк), мы рекомендуем использовать метод, известный как «оконный». Этот метод только отображает небольшое подмножество ваших строк в любой момент времени и может значительно сократить время, необходимое для повторной обработки компонентов, а также количества созданных узлов DOM.

React Virtualized - одна популярная библиотека окон. Он предоставляет несколько повторно используемых компонентов для отображения списков, сеток и табличных данных. Вы также можете создать свой собственный компонент окна, например, Twitter , если хотите что-то более приспособленное для конкретного случая использования вашего приложения.

Избегайте примирения

React строит и поддерживает внутреннее представление отображаемого пользовательского интерфейса. Он включает элементы React, которые вы возвращаете из своих компонентов. Это представление позволяет React избегать создания узлов DOM и доступа к существующим без необходимости, поскольку это может быть медленнее операций над объектами JavaScript. Иногда его называют «виртуальным DOM», но он работает аналогично с React Native.

Когда реквизит или состояние компонента изменяется, React решает, требуется ли фактическое обновление DOM, сравнивая вновь возвращенный элемент с ранее отображенным. Когда они не равны, React обновит DOM.

Теперь вы можете визуализировать эти повторные визуализации виртуальной DOM с помощью React DevTools:

В консоли разработчика выберите параметр «Highlight Updates» на вкладке «React». Взаимодействуйте со своей страницей, и вы должны увидеть, что цветные границы мгновенно появляются вокруг любых компонентов, которые были повторно отображены. Это позволяет вам выявлять повторные рендеры, которые не были необходимы.

Рассмотрим пример Todo списка, когда мы вводим второе todo, первое todo также мигает на экране при каждом нажатии клавиши. Это означает, что он повторно отображается Реатом вместе с входом. Это иногда называют «потерянным» рендерингом. Мы знаем, что это необязательно, потому что первый контент todo не изменился, но React этого не знает.

Несмотря на то, что React обновляет только измененные узлы DOM, повторный показ занимает некоторое время. Во многих случаях это не проблема, но если замедление заметно, вы можете ускорить все это, переопределив функцию жизненного цикла shouldComponentUpdate, которая запускается до начала процесса повторного рендеринга. Реализация этой функции по умолчанию возвращается true, оставив React для выполнения обновления:

shouldComponentUpdate(nextProps, nextState) {
  return true;
}

Если вы знаете , что в некоторых ситуациях компонент не нужно обновлять, вы можете вернуться false из shouldComponentUpdate вместо того , чтобы пропустить весь процесс рендеринга, включая вызов render() на этом компоненте и ниже.

В большинстве случаев вместо того, чтобы писать shouldComponentUpdate() вручную, вы можете наследовать React.PureComponent. Это эквивалентно реализации shouldComponentUpdate() с неглубоким сравнением текущего и предыдущего реквизита и состояния.

shouldComponentUpdate in Action

Вот поддерево компонентов. Для каждого из них SCU указывается, что shouldComponentUpdate возвращается, и vDOMEq указывает, эквивалентны ли отображаемые элементы React. Наконец, цвет круга указывает, должен ли компонент быть согласован или нет.

Так как shouldComponentUpdate возвращается false для поддерева, внедренного в C2, React не пытался отобразить C2 и, следовательно, даже не нужно ссылаться shouldComponentUpdate на C4 и C5.

Для C1 и C3 shouldComponentUpdate вернулись true, так что «React» должен был спуститься к листьям и проверить их. Для shouldComponentUpdate возврата C6 true, а так как отображаемые элементы не были эквивалентны, React должен был обновить DOM.

Последний интересный случай - C8. Реагирование должно было отобразить этот компонент, но поскольку возвращаемые им элементы React были равны ранее предоставленным, ему не нужно было обновлять DOM.

Обратите внимание, что React должен был делать DOM-мутации для C6, что было неизбежно. Для C8, он выручил путем сравнения отображаемых элементов React, а для поддерева C2 и C7, даже не пришлось сравнивать элементы по мере того, как мы выходили shouldComponentUpdate и render не вызывались.

Примеры

Если единственный способ изменения вашего компонента - когда изменяется переменная props.color или state.count переменная, вы можете shouldComponentUpdate проверить, что:

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

В этом коде shouldComponentUpdate просто проверяется, есть ли какие-либо изменения в props.color или state.count. Если эти значения не изменяются, компонент не обновляется. Если ваш компонент стал более сложным, вы можете использовать аналогичную схему для «мелкого сравнения» между всеми полями propsи stateопределить, должен ли компонент обновляться. Этот шаблон достаточно распространен, что React предоставляет помощника для использования этой логики - просто наследуйте React.PureComponent. Таким образом, этот код - более простой способ добиться того же:

class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState(state => ({count: state.count + 1}))}>
        Count: {this.state.count}
      </button>
    );
  }
}

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

Это может быть проблемой для более сложных структур данных. Например, предположим, что вы хотите, чтобы ListOfWords компонент отображал список слов, разделенных запятыми, с родительским WordAdder компонентом, который позволяет вам щелкнуть кнопку, чтобы добавить слово в список. Этот код обычно не работает правильно:

class ListOfWords extends React.PureComponent {
  render() {
    return <div>{this.props.words.join(',')}</div>;
  }
}

class WordAdder extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      words: ['marklar']
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    // This section is bad style and causes a bug
    const words = this.state.words;
    words.push('marklar');
    this.setState({words: words});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleClick} />
        <ListOfWords words={this.state.words} />
      </div>
    );
  }
}

Проблема в том, что PureComponent будет делать простое сравнение между старыми и новыми значениями this.props.words. Поскольку этот код мутирует wordsмассив в handleClick методе WordAdder, старые и новые значения this.props.words будут сравниваться как равные, хотя фактические слова в массиве изменились. Таким ListOfWords образом, не будет обновляться, хотя у него есть новые слова, которые должны быть визуализированы.

Сила не мутирующих данных

Самый простой способ избежать этой проблемы - избежать мутирующих значений, которые вы используете в качестве реквизита или состояния. Например, handleClick описанный выше метод можно переписать, используя concat:

handleClick() {
  this.setState(prevState => ({
    words: prevState.words.concat(['marklar'])
  }));
}

ES6 поддерживает синтаксис распространения для массивов, который может сделать это проще. Если вы используете приложение Create React, этот синтаксис доступен по умолчанию.

handleClick() {
  this.setState(prevState => ({
    words: [...prevState.words, 'marklar'],
  }));
};

Вы также можете переписать код, который мутирует объекты, чтобы избежать мутации, аналогичным образом. Например, предположим, что у нас есть объект с именем, colormap и мы хотим написать функцию, которая colormap.right будет изменяться 'blue'. Мы могли бы написать:

function updateColorMap(colormap) {
  colormap.right = 'blue';
}

Чтобы написать это без мутации исходного объекта, мы можем использовать метод Object.assign:

function updateColorMap(colormap) {
  return Object.assign({}, colormap, {right: 'blue'});
}

updateColorMap теперь возвращает новый объект, а не мутирует старый. Object.assign находится в ES6 и требует полиполнения. Существует предложение JavaScript для добавления свойств распространения объекта, чтобы упростить обновление объектов без мутации:

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

Если вы используете приложение Create React, оба Object.assign и синтаксис распространения объекта доступны по умолчанию.

Использование неизменяемых структур данных

Immutable.js - еще один способ решить эту проблему. Он предоставляет неизменные, постоянные коллекции, которые работают через структурный обмен:

  • Неизменяемость (Immutable): после создания коллекция не может быть изменена в другой момент времени.
  • Настойчивый (Persistent): новые коллекции могут быть созданы из предыдущей коллекции и мутации, такие как set. Оригинальная коллекция по-прежнему действует после создания новой коллекции.
  • Структурный обмен (Structural Sharing): новые коллекции создаются с использованием такой же структуры, как и исходная коллекция, что позволяет сократить количество копий до минимума для повышения производительности.

Неизменность делает отслеживание изменений дешевым. Изменение всегда приведет к созданию нового объекта, поэтому нам нужно только проверить, изменилась ли ссылка на объект. Например, в этом обычном JavaScript-коде:

const x = { foo: 'bar' };
const y = x;
y.foo = 'baz';
x === y; // true

Хотя yбыл отредактирован, поскольку это ссылка на тот же объект, как xэто сравнение возвращается true. Вы можете написать аналогичный код с immutable.js:

const SomeRecord = Immutable.Record({ foo: null });
const x = new SomeRecord({ foo: 'bar' });
const y = x.set('foo', 'baz');
const z = x.set('foo', 'bar');
x === y; // false
x === z; // true

В этом случае, поскольку новая ссылка возвращается при мутации x, мы можем использовать контрольную проверку равенства, (x === y) чтобы проверить, что новое значение, хранящееся, yотличается от исходного значения, хранящегося в x.

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

Неизменяемые структуры данных предоставляют вам дешевый способ отслеживания изменений объектов, которые все, что нам нужно реализовать shouldComponentUpdate. Это может обеспечить вам хороший прирост производительности.