Урок 09

Оптимизация компонентов

Мобильные устройства слабее, чем компьютеры. Оптимизация важна.

memo

Мемоизирует компонент. Перерисовывается только при изменении пропсов:

import { memo } from 'react';

export const ItemCard = memo(function ItemCard({ item }: { item: Item }) {
    return (
        <View>
            <Text>{item.title}</Text>
        </View>
    );
});

Работает только если пропсы не меняются. Объекты и функции нужно мемоизировать отдельно.

useCallback

Мемоизация функций:

const handlePress = useCallback((id: number) => {
    navigate('Details', { id });
}, [navigate]);

Без useCallback функция создаётся заново при каждом рендере, и memo не работает.

useMemo

Мемоизация значений:

const sorted = useMemo(
    () => [...items].sort((a, b) => a.title.localeCompare(b.title)),
    [items]
);

FlatList оптимизация

<FlatList
    data={items}
    keyExtractor={keyExtractor}
    renderItem={renderItem}
    initialNumToRender={10}
    maxToRenderPerBatch={10}
    windowSize={5}
    removeClippedSubviews
    getItemLayout={(_, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
    })}
/>

getItemLayout — если элементы одинаковой высоты.

Мемоизация renderItem

const renderItem = useCallback(
    ({ item }: { item: Item }) => <ItemCard item={item} />,
    []
);

const keyExtractor = useCallback((item: Item) => item.id.toString(), []);

React.memo с сравнением

const ItemCard = memo(
    function ItemCard({ item }: { item: Item }) {
        return <View><Text>{item.title}</Text></View>;
    },
    (prev, next) => prev.item.id === next.item.id && prev.item.title === next.item.title
);

Кастомное сравнение. Используй осторожно — легко ошибиться.

Не создавай объекты в пропсах

// Плохо — новый объект каждый рендер
<ItemCard config={{ showTitle: true }} />

// Хорошо — константа
const config = { showTitle: true };
<ItemCard config={config} />

Избегай inline-функций в FlatList

// Плохо
<FlatList renderItem={({ item }) => <Card item={item} />} />

// Хорошо
const renderItem = useCallback(({ item }) => <Card item={item} />, []);
<FlatList renderItem={renderItem} />

Разделение компонентов

Если компонент большой — раздели на части. Меньше перерисовок:

function Screen() {
    const [tab, setTab] = useState('home');

    return (
        <>
            <Header /> {/* не зависит от tab */}
            <TabBar current={tab} onChange={setTab} />
            {tab === 'home' ? <HomeTab /> : <ProfileTab />}
        </>
    );
}

Header не перерисовывается при смене вкладки.

Избегай лишних состояний

// Плохо — вычисляемое в state
const [fullName, setFullName] = useState('');
useEffect(() => setFullName(`${first} ${last}`), [first, last]);

// Хорошо — вычисляемое на месте
const fullName = `${first} ${last}`;

Reanimated на UI-потоке

Анимации через Reanimated работают в UI-потоке. Не блокируют JS.

const offset = useSharedValue(0);

const style = useAnimatedStyle(() => ({
    transform: [{ translateX: withSpring(offset.value) }],
}));

Избегай тяжёлых вычислений в рендере

// Плохо
function Component({ items }) {
    const sorted = items.sort((a, b) => a.price - b.price); // мутация + каждый рендер
    return <List items={sorted} />;
}

// Хорошо
function Component({ items }) {
    const sorted = useMemo(() => [...items].sort((a, b) => a.price - b.price), [items]);
    return <List items={sorted} />;
}

React DevTools Profiler

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

Flashlight

Инструмент для измерения FPS и производительности на реальном устройстве. Показывает, где проседает.

Итоги

  • memo, useCallback, useMemo
  • Оптимизация FlatList
  • Мемоизация renderItem
  • Не создавай объекты в пропсах
  • Разделяй большие компоненты
  • Reanimated для плавных анимаций