← Назад к вопросам

Как оптимизируешь Relayout при добавлении карточки в середину списка карточек?

2.0 Middle🔥 172 комментариев
#JavaScript Core

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Оптимизация Relayout при добавлении карточки в середину списка достигается через использование ключей в React, виртуализации списков, CSS Grid/Flexbox, RequestAnimationFrame и минимизацию DOM операций.

1. Правильное использование ключей (Keys)

// ❌ Плохо — индекс как ключ приводит к пересчёту всех элементов
function CardList({ cards }) {
  return (
    <div className="card-grid">
      {cards.map((card, index) => (
        <Card key={index} card={card} />
      ))}
    </div>
  );
}

// ✅ Хорошо — уникальный идентификатор как ключ
function CardList({ cards }) {
  return (
    <div className="card-grid">
      {cards.map((card) => (
        <Card key={card.id} card={card} />
      ))}
    </div>
  );
}

React использует ключи для определения, какие элементы изменились. С правильными ключами вставка в середину затрагивает только новую карточку, а не весь список.

2. Виртуализация списка (Virtualization)

// Используем react-window для больших списков
import { FixedSizeList as List } from 'react-window';

function VirtualCardList({ cards }) {
  const Row = ({ index, style }) => (
    <div style={style}>
      <Card card={cards[index]} />
    </div>
  );

  return (
    <List
      height={600}
      itemCount={cards.length}
      itemSize={200}
      width="100%"
    >
      {Row}
    </List>
  );
}

Виртуализация рендерит только видимые карточки, значительно снижая количество DOM операций.

3. Оптимизация CSS Layout

// ✅ CSS Grid с auto-flow для эффективного переноса
const cardGridStyles = `
  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 1rem;
    grid-auto-flow: dense; /* Оптимизирует заполнение пространства */
  }

  /* Избегаем переполнения */
  .card {
    contain: layout style paint; /* CSS containment для изоляции */
  }
`;

CSS containment изолирует элемент от остального документа, что предотвращает пересчёт всего дерева стилей.

4. RequestAnimationFrame для батчинга обновлений

function CardList({ cards, onAddCard }) {
  const [displayCards, setDisplayCards] = useState(cards);

  const addCardOptimized = (newCard, position) => {
    // Батчим обновление через RAF
    requestAnimationFrame(() => {
      const updated = [...displayCards];
      updated.splice(position, 0, newCard);
      setDisplayCards(updated);
    });
  };

  return (
    <div className="card-grid">
      {displayCards.map((card) => (
        <Card key={card.id} card={card} />
      ))}
    </div>
  );
}

5. Мемоизация компонента карточки

// Предотвращаем ненужные перерендеры соседних карточек
const Card = React.memo(({ card, index }) => {
  return (
    <div className="card">
      <h3>{card.title}</h3>
      <p>{card.description}</p>
    </div>
  );
}, (prevProps, nextProps) => {
  // Пользовательская функция сравнения
  return (
    prevProps.card.id === nextProps.card.id &&
    prevProps.index === nextProps.index
  );
});

6. Использование useDeferredValue для отложенных обновлений

import { useDeferredValue, useState } from 'react';

function CardList() {
  const [cards, setCards] = useState([]);
  const deferredCards = useDeferredValue(cards);

  const addCard = (newCard, position) => {
    // Добавляем карточку немедленно в UI
    setCards((prev) => {
      const updated = [...prev];
      updated.splice(position, 0, newCard);
      return updated;
    });
    // Дорогие расчёты выполняются с deferredCards
  };

  return (
    <div className="card-grid">
      {deferredCards.map((card) => (
        <Card key={card.id} card={card} />
      ))}
    </div>
  );
}

7. Минимизация DOM операций

// ✅ Хорошо — одна операция вставки
function insertCardOptimized(cardElement, container, position) {
  const children = container.children;
  
  if (position >= children.length) {
    container.appendChild(cardElement);
  } else {
    container.insertBefore(cardElement, children[position]);
  }
}

// Если нужно вставить множество карточек:
const fragment = document.createDocumentFragment();
newCards.forEach(card => {
  fragment.appendChild(createCardElement(card));
});
container.appendChild(fragment); // Одна операция вместо N

8. Полный пример с оптимизациями

import { useState, useCallback, useMemo } from 'react';

const Card = React.memo(({ card }) => (
  <div className="card">
    <img src={card.image} alt={card.title} />
    <h3>{card.title}</h3>
  </div>
));

export function OptimizedCardList({ initialCards = [] }) {
  const [cards, setCards] = useState(initialCards);

  const addCardInMiddle = useCallback((newCard) => {
    setCards((prev) => {
      const position = Math.floor(prev.length / 2);
      const updated = [...prev];
      updated.splice(position, 0, newCard);
      return updated;
    });
  }, []);

  const memoizedCards = useMemo(() => cards, [cards]);

  return (
    <div className="card-grid">
      {memoizedCards.map((card) => (
        <Card key={card.id} card={card} />
      ))}
    </div>
  );
}

Ключевые техники оптимизации:

  1. Используй уникальные ID как ключи (не индексы)
  2. Мемоизируй компоненты Card через React.memo
  3. Используй CSS containment для изоляции стилей
  4. Виртуализируй большие списки через react-window
  5. Батчируй обновления через RequestAnimationFrame
  6. Минимизируй DOM операции через DocumentFragment
  7. Используй Grid с auto-flow-dense для умного переноса
  8. Профилируй Performance через DevTools Profiler
Как оптимизируешь Relayout при добавлении карточки в середину списка карточек? | PrepBro