Рендеринг и 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 перерисовывать дерево сколько угодно раз.