Для чего нужен атрибут Key при рендеринге списков?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужен атрибут key при рендеринге списков
key — это специальный атрибут React, который помогает идентифицировать элементы списка. Это один из самых важных инструментов для оптимизации производительности и предотвращения багов.
Как React определяет, какой элемент изменился
Когда React рендерит список без key, он использует позицию элемента в массиве (индекс) для отслеживания:
function UserList() {
const [users, setUsers] = useState([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" }
]);
return users.map((user) => (
<div>{user.name}</div> // БЕЗ key
));
}
Проблема: если удалить первого пользователя или переставить порядок, React не поймёт, что произошло, и может применить состояние не к тому элементу.
Почему это важно
1. Баг с input и state
function TodoList({ todos }) {
return todos.map((todo, index) => (
<div key={index}> {/* ПЛОХО: использование index как key */}
<input type="text" defaultValue={todo.text} />
<button>Удалить</button>
</div>
));
}
// Сценарий:
// 1. todos = ["Купить хлеб", "Помыть посуду", "Убраться"]
// 2. Пользователь вводит текст в первый input: "Купить хлеб - DONE"
// 3. Удаляем первый элемент
// 4. todos = ["Помыть посуду", "Убраться"]
// 5. БАГИ: input всё ещё показывает "Купить хлеб - DONE", потому что React
// подумал, что это всё ещё первый элемент (индекс 0)
Правильное решение:
function TodoList({ todos }) {
return todos.map((todo) => (
<div key={todo.id}> {/* ХОРОШО: используем уникальный id */}
<input type="text" defaultValue={todo.text} />
<button>Удалить</button>
</div>
));
}
2. Потеря состояния компонента
function ListItem({ id, name }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div>
<button onClick={() => setIsExpanded(!isExpanded)}>
{isExpanded ? "Скрыть" : "Показать"}
</button>
{isExpanded && <p>Подробности</p>}
</div>
);
}
function App() {
const [items, setItems] = useState([
{ id: 1, name: "Item A" },
{ id: 2, name: "Item B" }
]);
return (
<div>
<button onClick={() => setItems([{ id: 0, name: "New" }, ...items])}>
Добавить в начало
</button>
{items.map((item, index) => (
<ListItem key={index} id={item.id} name={item.name} />
{/* БЕЗ правильного key, expanded-состояние сбивается */}
))}
</div>
);
}
Когда добавляем новый элемент, React теряет track состояния (isExpanded) каждого компонента.
3. Производительность и ненужные ререндеры
function ExpensiveComponent({ id, data }) {
// Сложные вычисления
const result = useMemo(() => heavyCalculation(data), [data]);
return <div>{result}</div>;
}
function App() {
const [list, setList] = useState([
{ id: 1, data: "A" },
{ id: 2, data: "B" }
]);
// БЕЗ правильного key
return list.map((item, index) => (
<ExpensiveComponent key={index} id={item.id} data={item.data} />
));
// Когда элемент удаляется, все компоненты ререндерятся,
// потому что индексы сдвигаются
}
Правильное использование key
Правило 1: Используй уникальный ID
// ХОРОШО
const users = [
{ id: "user-1", name: "Alice" },
{ id: "user-2", name: "Bob" }
];
users.map((user) => (
<UserCard key={user.id} user={user} />
));
Правило 2: Не используй index как key (если список может изменяться)
// ПЛОХО: если список сортируется, удаляется, добавляется
items.map((item, index) => (
<Item key={index} item={item} /> // НЕПРАВИЛЬНО
));
// ХОРОШО: если список статичный и никогда не меняется
const staticList = ["Понедельник", "Вторник", "Среда"];
staticList.map((day, index) => (
<div key={index}>{day}</div> // OK, потому что список не меняется
));
Правило 3: Key должен быть стабильным
// ПЛОХО: key создается заново каждый рендер
items.map((item) => (
<Item key={Math.random()} item={item} /> // НЕПРАВИЛЬНО
));
// ПЛОХО: key зависит от данных, которые могут измениться
items.map((item) => (
<Item key={item.name} item={item} /> // Если name изменится, key"обновится
));
// ХОРОШО: стабильный ID, который не меняется
items.map((item) => (
<Item key={item.id} item={item} /> // ID стабилен в течение жизни элемента
));
React Reconciliation Algorithm
Вот как React использует key:
// Render 1:
[<Item key="a" />, <Item key="b" />, <Item key="c" />]
// Render 2 (переставили порядок):
[<Item key="c" />, <Item key="a" />, <Item key="b" />]
// БЕЗ key React подумает:
// "Три Item компонента, может быть они те же?"
// И применит props a к c, b к a, c к b (НЕПРАВИЛЬНО)
// С key React поймет:
// "c переместился на первое место, a на второе, b на третье"
// И переместит DOM элементы правильно
Реальный пример: сортировка
function UsersList({ users }) {
const [sortBy, setSortBy] = useState("name");
const sorted = [...users].sort((a, b) => {
if (sortBy === "name") return a.name.localeCompare(b.name);
return a.id - b.id;
});
return (
<div>
<button onClick={() => setSortBy("name")}>Сортировать по имени</button>
<button onClick={() => setSortBy("id")}>Сортировать по ID</button>
{sorted.map((user) => (
<UserItem key={user.id} user={user} /> {/* ПРАВИЛЬНО */}
))}
</div>
);
}
// С key={user.id} React правильно переместит элементы DOM
// Без key React может потерять состояние компонентов
Итог
key нужен для:
- Правильной идентификации элементов списка
- Сохранения состояния компонентов при изменении списка
- Оптимизации производительности (React не ререндерит лишние элементы)
- Избежания багов с потерей данных
Best Practice:
- Используй уникальный, стабильный ID
- Избегай Math.random() и индексов
- Никогда не создавай key динамически
Это одна из самых частых причин багов в React-приложениях, особенно когда список может изменяться.