Оптимизация производительности
Внутренне React использует несколько хитроумных техник для минимизации количества дорогостоящих операций с DOM, необходимых для обновления пользовательского интерфейса. Для многих приложений использование React обеспечит быстрый пользовательский интерфейс без особых усилий по оптимизации производительности. Тем не менее, есть несколько способов ускорить ваше приложение React.
Использование сборки для производства
Если вы проводите бенчмаркинг или сталкиваетесь с проблемами производительности в своих приложениях React, убедитесь, что тестируете с сжатой сборкой для производства.
По умолчанию React включает много полезных предупреждений. Эти предупреждения очень полезны в режиме разработки. Однако они увеличивают размер React и замедляют его работу, поэтому вы должны убедиться, что используете производственную версию при развертывании приложения.
Если вы не уверены, правильно ли настроен ваш процесс сборки, вы можете проверить это, установив React Developer Tools для Chrome. Если вы посетите сайт с React в режиме производства, иконка будет иметь тёмный фон:
Если вы посетите сайт с React в режиме разработки, иконка будет иметь красный фон:
Ожидается, что вы будете использовать режим разработки при работе над приложением и режим производства при развертывании приложения для пользователей.
Инструкции по созданию приложения для производства вы найдете ниже.
Create React App
Если ваш проект создан с помощью Create React App, запустите:
npm run build
Это создаст производственную сборку вашего приложения в папке build/ вашего проекта.
Помните, что это необходимо только перед развертыванием в производство. Для обычной разработки используйте npm start.
Сборки в один файл
Мы предлагаем готовые к производству версии React и React DOM в виде одного файла:
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
Помните, что только файлы React, заканчивающиеся на .production.min.js, подходят для производства.
Brunch
Для наиболее эффективной сборки Brunch в режиме производства установите плагин terser-brunch:
# If you use npm npm install --save-dev terser-brunch # If you use Yarn yarn add --dev terser-brunch
Затем, чтобы создать сборку для производства, добавьте флаг -p к команде build.
brunch build -p
Помните, что вам нужно сделать это только для сборок в режиме производства. Вы не должны передавать флаг -p или применять этот плагин в режиме разработки, так как это скроет полезные предупреждения React и значительно замедлит сборку.
Browserify
Для наиболее эффективной сборки Browserify в режиме производства установите несколько плагинов:
# If you use npm npm install --save-dev envify terser uglifyify # If you use Yarn yarn add --dev envify terser uglifyify
Чтобы создать сборку для производства, убедитесь, что вы добавили эти преобразования (порядок важен):
- Преобразование
envifyобеспечивает правильную установку среды сборки. Сделайте его глобальным (-g). - Преобразование
uglifyifyудаляет импорты разработки. Сделайте его также глобальным (-g). - Наконец, получившийся пакет передается в
terserдля ухудшения кода (почему это необходимо).
Например:
browserify ./index.js \ -g [ envify --NODE_ENV production ] \ -g uglifyify \ | terser --compress --mangle > ./bundle.js
Помните, что это необходимо только для сборок в режиме производства. Вы не должны применять эти плагины в режиме разработки, так как они скроют полезные предупреждения React и значительно замедлят сборку.
Rollup
Для наиболее эффективной сборки Rollup в режиме производства установите несколько плагинов:
# If you use npm npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser # If you use Yarn yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser
Чтобы создать сборку для производства, убедитесь, что вы добавили эти плагины (порядок важен):
- Плагин
replaceобеспечивает правильную установку среды сборки. - Плагин
commonjsобеспечивает поддержку CommonJS в Rollup. - Плагин
terserсжимает и ухудшает конечный пакет.
plugins: [
// ...
require('rollup-plugin-replace')({
'process.env.NODE_ENV': JSON.stringify('production')
}),
require('rollup-plugin-commonjs')(),
require('rollup-plugin-terser')(),
// ...
] Для примера полной настройки см. этот gist.
Помните, что это необходимо только для сборок в режиме производства. Вы не должны применять плагин terser или плагин replace со значением 'production' в режиме разработки, так как это скроет полезные предупреждения React и значительно замедлит сборку.
Webpack
Примечание:
Если вы используете Create React App, следуйте инструкциям выше.
Этот раздел актуален только если вы настраиваете webpack напрямую.
Webpack v4+ по умолчанию сжимает ваш код в режиме производства.
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
optimization: {
minimizer: [new TerserPlugin({ /* additional options here */ })],
},
}; Дополнительную информацию можно найти в документации webpack.
Помните, что это необходимо только для сборок в режиме производства. Вы не должны применять TerserPlugin в режиме разработки, так как это скроет полезные предупреждения React и значительно замедлит сборку.
Профилирование компонентов с помощью профилировщика DevTools
react-dom 16.5+ и react-native 0.57+ предоставляют расширенные возможности профилирования в режиме разработки с помощью профилировщика React DevTools. Обзор профилировщика можно найти в сообщении блога “Представление профилировщика React”. Также доступен видеообзор профилировщика на YouTube.
Если вы еще не установили React DevTools, вы можете найти их здесь:
Примечание
Также доступна производственная сборка профилирования
react-domкакreact-dom/profiling. Подробнее о том, как использовать этот пакет, можно узнать на странице fb.me/react-profiling
Примечание
До React 17 мы используем стандартный API User Timing для профилирования компонентов с помощью вкладки производительности Chrome. Более подробный обзор можно найти в статье Бена Шварца.
Визуализация длинных списков
Если ваше приложение отображает длинные списки данных (сотни или тысячи строк), мы рекомендуем использовать технику «оконного отображения». Эта техника отображает только небольшой поднабор строк в данный момент и может значительно уменьшить время повторного отображения компонентов, а также количество созданных узлов DOM.
react-window и react-virtualized — популярные библиотеки оконного отображения. Они предоставляют несколько многоразовых компонентов для отображения списков, таблиц и табличных данных. Вы также можете создать свой собственный компонент оконного отображения, как это сделала компания Twitter , если вам нужно что-то более подходящее для специфики вашего приложения.
Избегайте согласования
React создаёт и поддерживает внутреннее представление отображаемого пользовательского интерфейса. Он включает в себя React-элементы, которые вы возвращаете из своих компонентов. Это представление позволяет React избегать создания узлов DOM и доступа к существующим узлам, за исключением необходимых случаев, так как это может быть медленнее, чем операции с JavaScript-объектами. Иногда это называется «виртуальным DOM», но в React Native оно работает таким же образом.
Когда меняются свойства или состояние компонента, React определяет, необходима ли фактическая перерисовка DOM, сравнивая возвращённый элемент с ранее отображённым элементом. Если они не равны, React обновит DOM.
Хотя React обновляет только изменённые узлы DOM, перерисовка всё равно занимает некоторое время. Во многих случаях это не проблема, но если замедление заметно, вы можете ускорить всё это, переопределив функцию жизненного цикла shouldComponentUpdate, которая вызывается перед началом процесса перерисовки. Стандартная реализация этой функции возвращает true, оставляя React выполнять обновление:
shouldComponentUpdate(nextProps, nextState) {
return true;
} Если вы знаете, что в некоторых ситуациях ваш компонент не нуждается в обновлении, вы можете вернуть false из shouldComponentUpdate вместо этого, чтобы пропустить весь процесс рендеринга, включая вызов render() на этом компоненте и ниже.
В большинстве случаев вместо ручного написания shouldComponentUpdate() вы можете унаследовать от React.PureComponent. Это эквивалентно реализации shouldComponentUpdate() с помощью поверхностного сравнения текущих и предыдущих свойств и состояния.
shouldComponentUpdate в действии
Вот поддерево компонентов. Для каждого из них SCU указывает, что вернула shouldComponentUpdate, а vDOMEq указывает, были ли возвращенные React-элементы эквивалентны. Наконец, цвет кружка указывает, нужно ли было согласовывать компонент или нет.
Поскольку shouldComponentUpdate вернуло false для поддерева, укорененного в C2, React не пытался отобразить C2 и, следовательно, даже не должен был вызывать shouldComponentUpdate для C4 и C5.
Для C1 и C3 shouldComponentUpdate вернуло true, поэтому React должен был спуститься к листьям и проверить их. Для C6 shouldComponentUpdate вернуло true, и так как возвращенные элементы не были эквивалентны, React должен был обновить DOM.
Последний интересный случай — C8. React должен был отобразить этот компонент, но так как возвращенные 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(state => ({
words: state.words.concat(['marklar'])
}));
} ES6 поддерживает синтаксис разнесения массивов, что может упростить эту задачу. Если вы используете Create React App, этот синтаксис доступен по умолчанию.
handleClick() {
this.setState(state => ({
words: [...state.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 и требует полифилла.
Синтаксис разнесения объектов также упрощает обновление объектов без изменений:
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
} Эта функция была добавлена в JavaScript в ES2018.
Если вы используете Create React App, и синтаксис разнесения объектов доступны по умолчанию.
При работе со вложенными объектами обновление их неизменяемым образом может показаться сложным. Если вы столкнётесь с этой проблемой, обратите внимание на Immer или immutability-helper. Эти библиотеки позволяют писать легко читаемый код без потери преимуществ неизменяемости.
Полезная ли эта страница?
© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://17.reactjs.org/docs/optimizing-performance.html