← Назад к вопросам
Как сделать минимальное количество рендеров при получении карточек на странице?
2.0 Middle🔥 121 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблема: Избыточные рендеры
При отображении списка карточек компонент может переrendериться много раз без необходимости. Каждый ненужный рендер замедляет приложение, особенно при большом количестве карточек.
Способ 1: Мемоизация компонента (React.memo)
// Неправильно: каждая карточка перерендеривается
function CardItem({ card, onLike }) {
return (
<div className="card">
<h3>{card.title}</h3>
<button onClick={() => onLike(card.id)}>Like</button>
</div>
);
}
// Правильно: завернуть в React.memo
const CardItem = React.memo(function CardItem({ card, onLike }) {
console.log(`Rendering card ${card.id}`); // Лог для отладки
return (
<div className="card">
<h3>{card.title}</h3>
<button onClick={() => onLike(card.id)}>Like</button>
</div>
);
});
// React.memo сравнивает props и перерендеривает только если они изменились
Способ 2: useCallback для стабильных функций
// Неправильно: функция создаётся заново при каждом рендере
function CardsList({ cards }) {
const onLike = (cardId) => {
console.log(`Liked card ${cardId}`);
};
// onLike новая -> CardItem всегда перерендеривается!
return (
<div>
{cards.map(card => (
<CardItem key={card.id} card={card} onLike={onLike} />
))}
</div>
);
}
// Правильно: useCallback для сохранения функции
function CardsList({ cards }) {
const onLike = useCallback((cardId) => {
console.log(`Liked card ${cardId}`);
}, []); // Зависимости пусты, функция создаётся один раз
return (
<div>
{cards.map(card => (
<CardItem key={card.id} card={card} onLike={onLike} />
))}
</div>
);
}
// Если функция зависит от переменных:
function CardsList({ cards, userId }) {
const onLike = useCallback((cardId) => {
fetch(`/api/users/${userId}/likes/${cardId}`, {
method: "POST"
});
}, [userId]); // Функция пересоздаётся только если userId изменился
return (
<div>
{cards.map(card => (
<CardItem key={card.id} card={card} onLike={onLike} />
))}
</div>
);
}
Способ 3: Правильные ключи и виртуализация
// Неправильно: индекс как key
function CardsList({ cards }) {
return (
<div>
{cards.map((card, index) => (
<CardItem key={index} card={card} />
// При добавлении элемента индексы сдвигаются, перерендеры везде!
))}
</div>
);
}
// Правильно: уникальный id
function CardsList({ cards }) {
return (
<div>
{cards.map(card => (
<CardItem key={card.id} card={card} />
))}
</div>
);
}
// Виртуализация для больших списков:
import { FixedSizeList as List } from "react-window";
function VirtualizedCardsList({ cards }) {
const Row = ({ index, style }) => (
<div style={style}>
<CardItem card={cards[index]} />
</div>
);
return (
<List
height={600}
itemCount={cards.length}
itemSize={200}
width="100%"
>
{Row}
</List>
);
// Рендеритсялось только видимые элементы вместо всех
}
Способ 4: useMemo для дорогостоящих вычислений
// Неправильно: фильтр пересчитывается каждый рендер
function CardsList({ cards, filterText }) {
const filteredCards = cards.filter(card =>
card.title.toLowerCase().includes(filterText.toLowerCase())
);
return (
<div>
{filteredCards.map(card => (
<CardItem key={card.id} card={card} />
))}
</div>
);
}
// Правильно: useMemo
function CardsList({ cards, filterText }) {
const filteredCards = useMemo(() => {
return cards.filter(card =>
card.title.toLowerCase().includes(filterText.toLowerCase())
);
}, [cards, filterText]); // Пересчитываем только если зависимости изменились
return (
<div>
{filteredCards.map(card => (
<CardItem key={card.id} card={card} />
))}
</div>
);
}
Способ 5: Комбинированный пример
// CardItem.jsx - мемоизированный
const CardItem = React.memo(function CardItem({ card, onLike }) {
return (
<div className="card">
<h3>{card.title}</h3>
<button onClick={() => onLike(card.id)}>Like</button>
</div>
);
});
// CardsList.jsx - оптимизированный список
function CardsList({ initialCards }) {
const [cards, setCards] = useState(initialCards);
const [filterText, setFilterText] = useState("");
const onLike = useCallback((cardId) => {
setCards(cards.map(card =>
card.id === cardId ? { ...card, likes: card.likes + 1 } : card
));
}, [cards]);
const filteredCards = useMemo(() => {
return cards.filter(card =>
card.title.toLowerCase().includes(filterText.toLowerCase())
);
}, [cards, filterText]);
return (
<div>
<input
type="text"
placeholder="Filter cards..."
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
/>
<div className="cards-container">
{filteredCards.map(card => (
<CardItem key={card.id} card={card} onLike={onLike} />
))}
</div>
</div>
);
}
Способ 6: Ленивая загрузка (Infinite Scroll)
function InfiniteCardsList() {
const [cards, setCards] = useState([]);
const [page, setPage] = useState(0);
const [loading, setLoading] = useState(false);
const observerTarget = useRef(null);
const loadMore = useCallback(async () => {
if (loading) return;
setLoading(true);
try {
const response = await fetch(`/api/cards?page=${page}&limit=20`);
const newCards = await response.json();
setCards(prev => [...prev, ...newCards]);
setPage(prev => prev + 1);
} finally {
setLoading(false);
}
}, [page, loading]);
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting && !loading) {
loadMore();
}
},
{ threshold: 0.5 }
);
if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => {
if (observerTarget.current) {
observer.unobserve(observerTarget.current);
}
};
}, [loading, loadMore]);
return (
<div>
<div className="cards-grid">
{cards.map(card => (
<CardItem key={card.id} card={card} />
))}
</div>
<div ref={observerTarget}>
{loading && <p>Loading...</p>}
</div>
</div>
);
}
Чеклист оптимизации
- Используй React.memo для компонентов
- Используй useCallback для функций, передаваемых как props
- Используй useMemo для дорогостоящих вычислений
- Используй правильные ключи (уникальные id, не индексы)
- Рассмотри виртуализацию для больших списков
- Рассмотри ленивую загрузку (infinite scroll)
- Избегай inline объектов и функций в render