ПредыдущийстатьяГоворя о некоторых направлениях и методах оптимизации производительности React, эта статья добавит, как выполнять измерение и анализ производительности, а также представит некоторые инструменты и методы для анализа производительности React.
Предпосылка любой оптимизации производительности заключается в том, что вы должны выяснить «проблему производительности», чтобы вы могли целенаправленно оптимизировать ее. Думаю, оптимизацию производительности для React можно разделить на два этапа:
-
1. Этап анализа
- Узнайте перерендеренные компоненты, количество повторных рендеров, а также ресурсы и время, потраченное на повторный рендеринг через профайлер
- Обнаружение изменений. С помощью профилировщика мы можем узнать, «что перерисовывается, стоимость повторного рендеринга», а затем обнаружение изменений отвечает на вопрос: «Почему происходит повторный рендеринг?»
-
2. Этап оптимизации. На этапе оптимизации мы решаем проблемы, возникшие на этапе анализа. Решений много. Вы можете обратиться к сопутствующей статье этой статьиГоворя о направлении оптимизации производительности React>
План этой статьи
Ниже приведен пример кода, протестированного в этой статье.
Рекомендуется нажать на панель Preview
Open In New Window
, или нажмите кнопкуСвязь, практическая онлайн-практика
Анализатор
Проанализируйте, какие компоненты визуализируются и сколько времени и ресурсов потребляет визуализация. Основными инструментами являются официальные инструменты разработчика React и инструменты производительности Chrome.
React Devtool
Первое, что вы должны использовать, это официальные инструменты разработчика.React v16.5 представляет новую функцию Profiler, которая упрощает анализ процесса рендеринга компонентов, и очень интуитивно понятно, какие компоненты рендерятся.
Выделить обновление
Первый, самый простой и удобный способ определить, был ли компонент перерендерен, — «Выделить обновления»..
① Включить обновление выделения:
② Эффект операции следующий:
③ Выделив обновление, вы можете в основном определить, какие компоненты перерендерены, поэтому теперь мы добавляем React.memo в ListItem (см. пример PureList) и видим эффект:
Эффект очень очевиден, теперь будет обновляться только увеличивающийся ListItem, а при сортировке массива будет обновляться только компонент List.Так что «чистые компоненты» — это первая карта оптимизации React, и она же самая эффективная. .
Анализатор
если高亮更新
не может удовлетворить ваши потребности, такие какВам нужно точно знать, какие компоненты рендерятся, сколько времени уходит на рендеринг, сколько коммитов (рендеров) сделано и т.д., то нужно использовать анализатор.
① Сначала выберите узел, который должен собирать информацию об измерениях (обычно корневой узел выбирается по умолчанию, некоторые приложения могут иметь несколько деревьев компонентов, в настоящее время вам необходимо выбрать их вручную):
② Хорошо, нажмите «Запись», чтобы начать измерение.
③ Глядя на результаты измерений, давайте сначала разберемся с базовой структурой панели Profiler:
-
1️⃣ Это список коммитов. Список коммитов представляет собой коммит (который можно рассматривать как рендеринг) операций, которые произошли во время записи.Чтобы понять значение коммита, вам также необходимо понять основные принципы рендеринга React.
После v16 рендеринг компонентов React будет разделен на две фазы, а именно фазы рендеринга и фиксации.
- На этапе рендеринга решается, какие изменения необходимо внести, например, в DOM.. Как следует из названия, на этом этапе React вызовет функцию рендеринга и сравнит результат с результатом предыдущего рендеринга, чтобы вычислить очередь операций, которые необходимо изменить.
- этап фиксации. Также известна как фаза фиксации, на которой выполняются запросы на изменение, отличающиеся от фазы рендеринга. Например, вставка в DOM, обновление, удаление, сортировка и т. д. На этом этапе React также вызывает функции жизненного цикла componentDidMount и componentDidUpdate.
До v16 или в «подобных React» фреймворках, таких как Preact, не было различия между фазой рендеринга и фазой фиксации, то есть эти две фазы смешивались вместе и фиксировались во время сравнения. Заинтересованные читатели могут прочитать предыдущие работы автора.Изучите основы компонентов и хуков от Preact
переключить фиксацию:
-
2️⃣ Выбрать другие формы отображения графики,Например
Ranked 视图
, это представление сортирует компоненты по времени рендеринга:
-
3️⃣ Диаграмма пламениЭта картина на самом деледерево компонентов, Профилировщик использует цвета, чтобы отметить, какие компоненты повторно визуализируются.Как и в списке COMMIT, здесь имеет значение цвет, например, серый указывает на отсутствие рендеринга; что видно из потребления рендеринга:
黑色 > 黄色 > 蓝色
, с помощью 👆рейтингового графика вы можете интуитивно почувствовать значение между разными цветами
-
4️⃣ Подробная информация о выбранном в данный момент компоненте или коммите, вы можете просмотреть реквизиты и состояние при рендеринге компонента
Дважды щелкните конкретный компонент, чтобы подробно сравнить время, затраченное на каждую фиксацию:
-
5️⃣ Настройки
Кроме того, вы можете установить, отфильтровать Commit и отображать ли собственные элементы:
④ Теперь используйте Profiler для анализа процесса рендеринга PureList:
Подробное введение в Profiler см. в этом официальном блогеIntroducing the React Profiler>
Инструменты повышения производительности Chrome
До версии 16.5 мы обычно использовали собственную производительность Chrome для измерения производительности React:
React использует стандартныйUser Timing API
(Все браузеры, поддерживающие этот стандарт, можно использовать для анализа React) для записи операций, поэтому мы смотрим на процесс рендеринга React на вкладке «Время». React также намеренно использует теги эмодзи.
Инструмент производительности может быть не таким интуитивно понятным, как React Devtool, но он очень мощный, например,Если React-DevtoolFiddler, то производительность равнаWireshark, Использование производительности может быть использовано для обнаружения некоторых более глубоких проблем, которые могут потребовать от вас определенного понимания принципа реализации React, точно так же, как при использовании Wireshark вам необходимо понимать некоторые сетевые протоколы.
Таким образом, использование инструмента «Производительность» имеет следующие преимущества:
- Он может измерять и анализировать детали всего процесса рендеринга, может обнаруживать вызывающий процесс и использование некоторых конкретных методов, что удобно для обнаружения некоторых проблем глубокого уровня.
- Он может измерять и анализировать детали, такие как рисунок, макет и состав базового DOM. Легко найти проблемы с производительностью браузера
Производительность на самом деле является распространенным инструментом тестирования производительности, поэтому подробности не обсуждаются в этой статье. Подробная ссылка
- Profiling React performance with React 16 and Chrome Devtools
- Официальная производительность Chrome использует документацию
Другие инструменты
Описанных выше инструментов в принципе достаточно. В сообществе есть еще несколько популярных инструментов, но эти инструменты рано или поздно будут заменены официальными, и они не успевают за обновлениями React.
- react-addons-perfReact v16 больше не поддерживается, скажем так. доступна старая версия
- react-perf-devtoolБольше не активен, не рекомендуется
обнаружение изменений
Хорошо, мы уже знаем, какие проблемы существуют в нашем приложении с помощью инструмента анализа, и диагностируем, какие компоненты становятся бессмысленными. Следующий шаг — выяснить, почему компонент перерисовывается, и определить, почему компонент был обновлен.
Давайте сначала предположим, что наш компонент является «чистым компонентом», что означает, что мы думаем, что компонент будет повторно отображаться только при изменении состояния зависимостей компонента.Нечистые компоненты обсуждать не имеет смысла, потому что они будут перерисовываться всякий раз, когда изменяется состояние или родительские изменения.
Затем для «чистого компонента» обычно существуют следующие факторы, которые могут вызвать повторную визуализацию компонента.:
- props + stateНе сомневайтесь, здесь нам просто нужно обратить вниманиереквизит из внешних источников, Изменения внутреннего состояния обычно вызываются людьми, и их легче обнаружить.
- Mobx observable value, Если вы получите доступ к ответным данным, переданным mobx, будет установлена зависимость состояния, которая является неявной относительно реквизита и контекста.Чтобы обнаружить его изменения, нам может потребоваться использовать некоторые инструменты, предоставляемые mobx.
- Context. Изменения значения контекста заставят компонент повторно отображать
Обнаружение изменения реквизита
В прошлой статье я предлагал упростить пропсы, изменения пропсов простых компонентов легко предсказать, и их можно обнаружить даже невооруженным глазом. Кроме того, если вы используете Redux, если вы строго следуете лучшим практикам Redux и сотрудничаете с инструментами разработчика Redux, вы также можете интуитивно определить, какие состояния изменились.
Если вы не можете выполнить вышеуказанные условия, вам, возможно, придется полагаться на инструменты. был один раньшеwhy-did-you-updateбиблиотека,Жаль, что сейчас его почти не поддерживают (его можно использовать в более старых версиях). Эта библиотека использует обезьяньи патчи для расширения React, чтобы сравнивать и определять, какие свойства и состояние изменились:
Кто-то позже написал ссылку на почему-вы-обновилиwhy-did-you-render, Однако автор по-прежнему не оптимистичен в отношении этих реализаций расширения React с помощью обезьяньих патчей.Это зависит от деталей внутренней реализации React, а стоимость обслуживания слишком высока.
Если вы сейчас используете хук, его легко написать самостоятельно.use-why-did-you-update:
import { useEffect, useRef } from 'react';
export function useWhyDidYouUpdate(name: string, props: Record<string, any>) {
// ⚛️保存上一个props
const latestProps = useRef(props);
useEffect(() => {
if (process.env.NODE_ENV !== 'development') return;
const allKeys = Object.keys({ ...latestProps.current, ...props });
const changesObj: Record<string, { from: any; to: any }> = {};
allKeys.forEach(key => {
if (latestProps.current[key] !== props[key]) {
changesObj[key] = { from: latestProps.current[key], to: props[key] };
}
});
if (Object.keys(changesObj).length) {
console.log('[why-did-you-update]', name, changesObj);
} else {
// 其他原因导致组件渲染
}
latestProps.current = props;
}, Object.values(props));
}
использовать:
const Counter = React.memo(props => {
useWhyDidYouUpdate('Counter', props);
return <div style={props.style}>{props.count}</div>;
});
Если это компонент класса, вы можетеcomponentDidUpdate
Используйте что-то вроде приведенного выше, чтобы сравнить реквизит
обнаружение изменений mobx
Перерендеры, вызванные изменением реквизита, исключены, теперь давайте посмотрим, вызваны ли изменения реактивными данными mobx.Если ваша команда не использует mobx, вы можете пропустить этот раздел.
Прежде всего, будь то Redux или Mobx, мы должны сделать изменения состояния предсказуемыми., Поскольку Mobx не имеет надежного шаблона изменения данных, такого как Redux, Mobx непросто автоматически отслеживать, как данные изменяются. В mobx мы используем@action
Пометить операцию изменения состояния, но не может этого сделать с асинхронной операцией. К счастью, mobx запустили позжеflow
API👏.
Для Mobx сначала рекомендуется включить строгий режим, требующий, чтобы все изменения данных помещались в @action или поток:
import { configure } from 'mobx';
configure({ enforceActions: 'always' });
Определение операций изменения состояния
import { observable, action, flow } from 'mobx';
class CounterStore {
@observable count = 0;
// 同步操作
@action('increment count')
increment = () => {
this.count++;
};
// 异步操作
// 这是一个生成器,类似于saga的机制
fetchCount = flow(function*() {
const count = yield getCount();
this.count = count;
});
}
Хорошо с приведенным выше соглашением, теперь это можно сделать в консоли (через mobx-logger) илиИнструменты разработчика MobxОтслеживайте изменения в реактивных данных Mobx.
Если вы не будете следовать спецификациям, это будет пустой тратой времени, если возникнет проблема, но ее можно решить. Mobx также предоставляетtraceФункция для определения причины выполнения SideEffect:
export const ListItem = observer(props => {
const { item, onShiftDown } = props;
trace();
return <div className="list-item">{/*...*/}</div>;
});
Эффект запуска (увеличенное значение):
Обнаружение изменения контекста
ОК, если вы исключите реквизиты и изменения данных MobX и перезарядки, то 100% вызваны контекстом, потому что после изменения контекста изменяются, компонент будет вынужден рендером. Автор находится вГоворя о направлении оптимизации производительности ReactУпомянуты некоторые ошибки ContextAPI. Сначала исключим эти причины.
В настоящее время нет подходящего механизма для отслеживания изменений контекста, мы можем использовать приведенный вышеuseWhyDidYouUpdate
Таким же образом можно сравнить значение Context:
function useIsContextUpdate(contexts: object = {}) {
const latestContexts = useRef(contexts);
useEffect(() => {
if (process.env.NODE_ENV !== 'development') return;
const changedContexts: string[] = [];
for (const key in contexts) {
if (contexts[key] !== latestContexts.current[key]) {
changedContexts.push(key);
}
}
if (changedContexts.length) {
console.log(`[is-context-update]: ${changedContexts.join(', ')}`);
}
latestContexts.current = contexts;
});
}
Применение:
const router = useRouter();
const myContext = useContext(MyContext);
useIsContextUpdate({
router,
myContext,
});
Взаимодействие React Devtool
Это экспериментальная функция React Devtool. Взаимодействие, переведенное на китайский язык, означает «взаимодействие»? На самом деле цель этой вещи — отследить, «что вызвало обновление», что является обнаружением изменений, о котором мы упоминали выше. React надеется предоставить общий API для разработчиков или сторонних инструментов, чтобы разработчики могли интуитивно определить причину обновления:
На рисунке выше показано, что во время записи отслеживались четыре взаимодействия, а также время и продолжительность взаимодействия. Поскольку это все еще стадия идеи, давайте просто выберем несколько кодов API и посмотрим:
/** 跟踪状态变更 **/
import { unstable_trace as trace } from "scheduler/tracing";
class MyComponent extends Component {
handleLoginButtonClick = event => {
// 跟踪setState
trace("Login button click", performance.now(), () => {
this.setState({ isLoggingIn: true });
});
};
// render ...
}
/** 跟踪异步操作 **/
import {
unstable_trace as trace,
unstable_wrap as wrap
} from "scheduler/tracing";
trace("Some event", performance.now(), () => {
setTimeout(
wrap(() => {
// Do some async work
})
);
});
/** 跟踪初始化渲染 **/
trace("initial render", performance.now(), () => render(<Application />));
Хорошо, это конец статьи, если вы думаете, что можете нажать 👍