Рендеринг и JSX — Примеры
Пример 1 — Список с ключом по позиции
Ключ строк редактируемого списка берут по индексу — и после удаления второй строки введённый текст третьей оказывается не на месте. Скажите, для чего нужен ключ, и задайте его по чему-то, что переживает изменение.
function Tasks({ items, onRemove }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>
<input defaultValue={item.note} />
<button onClick={() => onRemove(item.id)}>Удалить</button>
</li>
))}
</ul>
);
}
Решение
function Tasks({ items, onRemove }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>
<input defaultValue={item.note} />
<button onClick={() => onRemove(item.id)}>Удалить</button>
</li>
))}
</ul>
);
}
После рендера React сравнивает новое дерево элементов с предыдущим и применяет только различия; в списке ключ нужен ему, чтобы решить, переместился элемент или изменился. Индекс говорит «то, что стоит на второй позиции», поэтому после удаления каждая следующая строка наследует узел DOM — и несохранённый текст — предыдущей. Ключ по устойчивому id позволяет каждому элементу следовать за своими данными; правило действует для любого списка, в который можно вставлять, переупорядочивать или укорачивать, то есть на практике для каждого списка, доступного пользователю.
Пример 2 — Имя компонента со строчной буквы
Компонент не рендерит вообще ничего, и никакой ошибки не появляется. Объясните, как React решает, компонент перед ним или DOM-элемент, затем исправьте имя.
function badge({ label }) {
return <span className="badge">{label}</span>;
}
export default function App() {
return <badge label="Черновик" />;
}
Решение
function Badge({ label }) {
return <span className="badge">{label}</span>;
}
export default function App() {
return <Badge label="Черновик" />;
}
JSX компилируется в вызовы функций, и имя в угловых скобках становится ссылкой на переменную только с заглавной буквы; тег со строчной передаётся строкой и рендерится как неизвестный HTML-элемент, поэтому браузер создаёт пустой строчный элемент, а функция компонента вообще не вызывается. Поскольку ничего не падает, ошибка выглядит как проблема стилей, а не имени. Это же рассуждение объясняет и остальную поверхность JSX: атрибуты следуют именованию JavaScript, поэтому атрибут class становится свойством для списка классов, и каждый элемент нужно закрывать. Рендер ещё и обязан оставаться чистым — те же входные данные должны давать тот же результат, — и именно это позволяет React перерисовывать дерево сколько угодно раз.