Вспоминаем, что такое профилирование
Если бы ваше приложение умело разговаривать, оно бы, вероятно, сразу пожаловалось: «меня рендерят слишком часто», «меня заставляют пересчитывать тяжёлую функцию каждую секунду» или «я не успеваю обновляться». Чтобы услышать эти жалобы и разобраться, где именно приложение страдает, и существует профилирование компонентов.
Профилирование — это процесс анализа производительности приложения, позволяющий выявить узкие места и избыточные ререндеры. С помощью встроенного профайлера в React DevTools можно увидеть:
- Какие компоненты рендерились и сколько времени на это ушло;
- Какие изменения привели к ререндеру;
- Где тратится слишком много ресурсов и как этого избежать.
Где тратится слишком много ресурсов и как этого избежать.
Установка и настройка React DevTools в Expo
В обычном React Native проекте часто используют Flipper. Но если вы работаете с Expo, вам повезло — вам ничего не нужно вручную интегрировать. Expo уже предоставляет поддержку React DevTools и включает в себя удобный интерфейс — Expo DevTools.
Что нужно сделать:
- Установите React DevTools на ваш компьютер (если ещё не установлены).
npm install --save-dev react-devtools - Запустите Expo-проект:
npx expo start
- Expo DevTools откроется в браузере. Там можно включить поддержку React DevTools и подключиться к приложению.
- Откройте React DevTools отдельно (через ярлык или вручную), и вы увидите ваше дерево компонентов.
- Перейдите на вкладку Profiler, нажмите "Record", выполните действия в приложении и остановите запись — всё готово к анализу.
Если вы используете Expo Go на телефоне, убедитесь, что и телефон, и компьютер находятся в одной Wi-Fi-сети.
Использование профайлера React DevTools
Вот пошагово, как работать с профайлером:
- Запуск профилирования: Откройте вкладку Profiler в React DevTools, нажмите кнопку Record, и начните взаимодействие с приложением: переходите по экранам, вводите текст, нажимайте кнопки.
- Поймайте рендер: Сделайте любое действие, которое может вызвать ререндер компонентов, затем нажмите Stop.
- Изучение результатов: Вы увидите граф с компонентами и временем их отрисовки. Цветные полосы показывают, какие компоненты "тяжёлые", и как часто они перерисовывались.
Например:
App - 20ms
- Header - 5ms
- TransactionList - 12ms
- TransactionItem x 10 - 1ms каждый
Интерпретация данных: как понять, что тормозит?
Тяжёлые компоненты
Если компонент consistently рендерится дольше 15-20ms, стоит задуматься. Например:
const HeavyComponent = () => {
const calculate = () => {
let total = 0;
for (let i = 0; i < 1000000; i++) total += i;
return total;
};
return <Text>{calculate()}</Text>;
};
Здесь каждое изменение вызывает долгий расчёт. Решение — useMemo:
const HeavyComponent = () => {
const total = useMemo(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) sum += i;
return sum;
}, []);
return <Text>{total}</Text>;
};
Лишние рендеры
Вы видите, что компонент перерендеривается без видимых причин? Возможно, ему передаются новые (по ссылке) пропсы каждый раз:
const MemoComponent = React.memo(({ value }: { value: number }) => {
console.log('Рендер');
return <Text>{value}</Text>;
});
const Parent = () => {
const [count, setCount] = useState(0);
return (
<>
<MemoComponent value={count} />
<Button title="+" onPress={() => setCount(count + 1)} />
</>
);
};
Если value не меняется, MemoComponent не должен рендериться. Но если вы передаёте функции, объекты или массивы без useCallback / useMemo, это может ломать мемоизацию.
Сложные списки и виртуализация
Если вы отрисовываете большой список (например, список транзакций), используйте FlatList вместо обычного .map:
const items = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);
return (
<FlatList
data={items}
keyExtractor={(item) => item}
renderItem={({ item }) => <Text>{item}</Text>}
/>
);
Или даже FlashList из @shopify/flash-list, если список действительно большой.
Практический пример: от плохого к хорошему
Плохо
{items.map((item) => (
<Text key={item}>{item}</Text>
))}
Все элементы будут перерисовываться на каждом обновлении.
Лучше
const Item = React.memo(({ label }: { label: string }) => {
console.log(`Рендер: ${label}`);
return <Text>{label}</Text>;
});
<FlatList
data={items}
keyExtractor={(item) => item}
renderItem={({ item }) => <Item label={item} />}
/>
Теперь Item будет рендериться только при изменении своих пропсов.
Типичные ошибки и советы
- Забыли остановить запись профайлера — и получили график на 10 минут, в котором ничего не понятно.
- Передаёте функции без useCallback — из-за этого
React.memoне работает. - Играетесь со стилями inline — если
style={{}}создаётся при каждом рендере, это ломает мемоизацию и вызывает лишние обновления. - Не используете FlatList при длинных списках — тормоза гарантированы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ