TransitionGroup для списков

Библиотека MUI (Material-UI) предоставляет мощные возможности для создания динамичных и отзывчивых интерфейсов. Одним из инструментов, который позволяет плавно анимировать элементы списка при их добавлении, удалении или изменении, является компонент TransitionGroup, который интегрируется с MUI через библиотеку react-transition-group. Он особенно полезен для списков с динамическим содержимым, таких как чаты, уведомления или галереи.


Основные концепции TransitionGroup

TransitionGroup не рендерит визуальные элементы сам по себе. Он управляет жизненным циклом анимации дочерних компонентов, используя внутренние события enter, exit и appear. Каждый элемент списка оборачивается в компонент Transition или его производные, например CSSTransition, для задания конкретной анимации.

Ключевые свойства TransitionGroup:

  • component – указывает тип HTML-элемента, который обернёт все дочерние компоненты (по умолчанию div).
  • children – элементы, которые будут анимироваться.
  • appear – если true, элементы будут анимироваться при первоначальном появлении.

Интеграция с MUI List

Для примера возьмём компонент List из MUI и реализуем анимацию добавления и удаления элементов.

import React, { useState } from "react";
import { List, ListItem, ListItemText, Button } from "@mui/material";
import { TransitionGroup, CSSTransition } from "react-transition-group";
import "./listAnimations.css";

export default function AnimatedList() {
  const [items, setItems] = useState(["Элемент 1", "Элемент 2", "Элемент 3"]);

  const addItem = () => {
    const nextIndex = items.length + 1;
    setItems([...items, `Элемент ${nextIndex}`]);
  };

  const removeItem = (index) => {
    setItems(items.filter((_, i) => i !== index));
  };

  return (
    <div>
      <Button variant="contained" onCl ick={addItem}>Добавить элемент</Button>
      <List>
        <TransitionGroup>
          {items.map((item, index) => (
            <CSSTransition
              key={item}
              timeout={300}
              classNames="fade"
            >
              <ListItem
                secondaryAction={
                  <Button onCl ick={() => removeItem(index)}>Удалить</Button>
                }
              >
                <ListItemText primary={item} />
              </ListItem>
            </CSSTransition>
          ))}
        </TransitionGroup>
      </List>
    </div>
  );
}

Настройка CSS-анимаций

Файл listAnimations.css определяет стили для классов, которые создает CSSTransition: fade-enter, fade-enter-active, fade-exit, fade-exit-active.

.fade-enter {
  opacity: 0;
  transform: translateY(-10px);
}
.fade-enter-active {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 300ms, transform 300ms;
}
.fade-exit {
  opacity: 1;
  transform: translateY(0);
}
.fade-exit-active {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 300ms, transform 300ms;
}

Особенности:

  • fade-enter – начальное состояние при добавлении элемента.
  • fade-enter-active – состояние анимации, которое применяется после начала перехода.
  • fade-exit – начальное состояние перед удалением.
  • fade-exit-active – состояние анимации при удалении.

Использование MUI Collapse для плавных эффектов

MUI предлагает компонент Collapse, который можно использовать вместе с TransitionGroup для создания более естественных вертикальных анимаций, особенно в списках с переменной высотой элементов.

import { Collapse } from "@mui/material";

<TransitionGroup>
  {items.map((item) => (
    <Collapse key={item}>
      <ListItem>
        <ListItemText primary={item} />
      </ListItem>
    </Collapse>
  ))}
</TransitionGroup>

Преимущества использования Collapse:

  • Автоматическое вычисление высоты элемента для плавного открытия и закрытия.
  • Минимизация необходимости писать CSS-анимации вручную.
  • Лучшая интеграция с темами MUI и адаптивной версткой.

Рекомендации по производительности

  1. Использовать уникальные ключи – ключи элементов должны быть стабильными и уникальными, иначе TransitionGroup не сможет корректно отслеживать анимации.
  2. Минимизировать сложные ререндеры – анимации в больших списках могут влиять на производительность, поэтому желательно оптимизировать дочерние компоненты.
  3. Комбинировать с MUI ListItemSecondaryAction – это позволяет добавлять кнопки или иконки для удаления/редактирования без нарушения анимации.

Сложные сценарии: фильтрация и сортировка

При фильтрации или сортировке списка важно правильно управлять ключами и состоянием, чтобы элементы, которые остаются, не анимировались заново. Для этого лучше хранить массив с уникальными идентификаторами и использовать их в качестве key.

items.map(item => (
  <CSSTransition key={item.id} timeout={300} classNames="fade">
    <ListItem>
      <ListItemText primary={item.text} />
    </ListItem>
  </CSSTransition>
));

Поддержка SSR (Server-Side Rendering)

TransitionGroup и CSSTransition совместимы с SSR, но анимации на сервере не выполняются. Поэтому для элементов, которые изначально рендерятся на сервере, можно использовать проп appear={true} или управлять стилями через MUI sx/Theme, чтобы элементы выглядели корректно до завершения клиентской анимации.


Интеграция с темами MUI

Стили анимации можно комбинировать с системой тем MUI:

<ListItem
  sx={{
    bgcolor: "background.paper",
    "&.fade-enter": { opacity: 0 },
    "&.fade-enter-active": { opacity: 1, transition: "opacity 300ms" },
  }}
>
  <ListItemText primary={item} />
</ListItem>

Это позволяет создавать единый визуальный стиль анимаций, который автоматически реагирует на светлую или тёмную тему.


TransitionGroup в MUI позволяет создавать интерактивные списки с плавными и адаптивными анимациями, интегрируя react-transition-group, MUI-компоненты и темы, обеспечивая высокое качество UX в динамических интерфейсах.