Урок 06

Списки и ключи

Списки — одна из самых частых задач. Массив данных превращается в набор элементов через map.

Базовый список

const fruits = ['Яблоко', 'Банан', 'Вишня'];

function List() {
    return (
        <ul>
            {fruits.map((fruit) => (
                <li>{fruit}</li>
            ))}
        </ul>
    );
}

Но React выдаст предупреждение: каждый элемент списка должен иметь key.

Зачем key

Когда список обновляется, React должен понять, какой элемент добавился, какой удалился, а какой переехал. Без key React сравнивает элементы по позиции — и может обновить не то, что нужно.

Правильный key

{fruits.map((fruit) => (
    <li key={fruit}>{fruit}</li>
))}

key — уникальный идентификатор среди соседей. Если есть ID из БД — используй его:

{users.map((user) => (
    <li key={user.id}>{user.name}</li>
))}

Не используй индекс

Использование индекса массива как key — частая ошибка:

{items.map((item, index) => (
    <li key={index}>{item}</li>
))}

Проблема проявляется при сортировке, удалении, добавлении в середину: React запутается и может показать неправильные данные. Использовать индекс допустимо только для статических списков, которые никогда не меняются.

Список объектов

type User = {
    id: number;
    name: string;
    email: string;
};

function UserList({ users }: { users: User[] }) {
    return (
        <ul>
            {users.map((user) => (
                <li key={user.id}>
                    {user.name} — {user.email}
                </li>
            ))}
        </ul>
    );
}

Список компонентов

function CardList({ posts }: { posts: Post[] }) {
    return (
        <div className="grid">
            {posts.map((post) => (
                <Card key={post.id} post={post} />
            ))}
        </div>
    );
}

Фильтрация перед рендером

map работает вместе с filter:

{publishedPosts
    .filter((post) => post.status === 'published')
    .map((post) => (
        <Card key={post.id} post={post} />
    ))}

Сортировка

{[...posts]
    .sort((a, b) => a.title.localeCompare(b.title))
    .map((post) => (
        <Card key={post.id} post={post} />
    ))}

Обрати внимание на [...posts] — sort мутирует массив, а мутировать пропсы нельзя.

Пустой список

Если данных нет — покажи сообщение:

function UserList({ users }: { users: User[] }) {
    if (users.length === 0) {
        return <p>Пользователей нет</p>;
    }

    return (
        <ul>
            {users.map((user) => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

Вложенные списки

Каждый уровень имеет свой key:

{categories.map((category) => (
    <div key={category.id}>
        <h3>{category.name}</h3>
        <ul>
            {category.items.map((item) => (
                <li key={item.id}>{item.name}</li>
            ))}
        </ul>
    </div>
))}

Итоги

  • Списки рендерятся через map
  • Каждый элемент должен иметь key
  • key — уникальный ID, а не индекс
  • Фильтрация и сортировка — до map
  • Пустой список нужно обрабатывать