Различные способы добавить ключ к элементу JSX в цикле в React

Я работаю над реакцией уже больше года. Я в основном играл с итерацией массива, используя.map, .forEach, .filter или используя Object.keys и Object.values, если это объект.

Но каковы разные способы добавления уникального ключа в элемент jsx. Вот то, к чему я привык до сих пор

Использование уникального идентификатора из данных в качестве ключа к ключевому слову:

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];

render(){
  const items = data.map(item => {
    return <span key={item.id}>{item.name}</span>;
  }
  return(
     <div>
        {items}
     </div>
  )
}

Использование индекса в качестве ключа для ключевого слова:

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];

render(){
  const items = data.map((item, i) => {
    let keyValue = i+1;
    return <span key={keyValue}>{item.name}</span>;
  }
  return(
     <div>
        {items}
     </div>
  )
}

Есть ли другие способы добавить уникальный ключ к элементу jsx помимо того, что я упомянул выше, и какой из них наиболее эффективен и рекомендован?

4 ответа

Решение

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

Есть много способов написания ключей, и некоторые будут работать лучше, чем другие.

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

https://reactjs.org/docs/reconciliation.html

tl; dr Вводит эвристику для сравнения деревьев Virtual DOM, чтобы сделать это сравнение O(n) с n узлами этого дерева VDOM. Эту эвристику можно разделить на следующие пункты:

  • Компоненты другого типа создадут новое дерево: это означает, что при сравнении старого дерева с новым, если примиритель обнаружит, что узел действительно изменил свой тип (например, <Button /> в <NotButton />), приведет к тому, что наш Button будет размонтирован с дочерними элементами, а NotButton - с дочерними.
  • Мы можем подсказать React о том, как экземпляры сохраняются в VDOM, избегая их повторного создания. Эти подсказки предоставляются нами с ключами. После принятия решения о том, следует ли сохранить экземпляр в узле (поскольку его тип остается прежним), примиритель будет выполнять итерацию по дочерним узлам этого узла, чтобы сравнить их.

Предположим теперь, что у нас есть это:

<div>
  <Button title="One" />
  <Button title="Two" />
</div>

И мы хотели бы добавить кнопку в DOM на следующем рендере, скажем

<div>
  <Button title="Zero" />
  <Button title="One" />
  <Button title="Two" />
</div>

Алгоритм будет выглядеть следующим образом:

  • Сравнивает <divs> в обоих VDOM. Поскольку они имеют одинаковый тип, нам не нужно заново создавать новое дерево. Реквизиты те же, поэтому на данный момент нет изменений, которые можно применить к DOM.
  • кнопка One сравнивает с Zero, Reconciler обнаруживает, что здесь произошла смена реквизита, затем обновляет DOM с этим названием.
  • кнопка Two сравнивает с One, Reconcilier также обнаруживает изменение реквизита и использует DOM для записи этого изменения.
  • Обнаруживает, что новый Button добавляется как последний дочерний элемент, поэтому создает новый Button экземпляр в VDOM и записать это изменение в DOM.

Обратите внимание, что они выполняют много операций в DOM, поскольку сравнивают компоненты по их индексу.

Теперь мы можем исправить это поведение, сообщив нашему посреднику, что эти экземпляры следует использовать повторно. Теперь, давайте иметь это:

<div>
  <Button title="One" key="One" />
  <Button title="Two" key="Two" />
</div>

И мы хотели бы добавить кнопку в DOM на следующем рендере, скажем

<div>
  <Button title="Zero" key="Zero" />
  <Button title="One" key="One" />
  <Button title="Two" key"Two" />
</div>

Алгоритм будет выглядеть следующим образом:

  • Сравнивает <divs> в обоих VDOM. Поскольку они имеют одинаковый тип, нам не нужно заново создавать новое дерево. Реквизиты те же, поэтому на данный момент нет изменений, которые можно применить к DOM.
  • Берет первого ребенка из детей. 'Это Button ', говорит примиритель. "И есть ключ" ("One"). Затем ищет детей с тем же ключом в новом списке детей. "О, я столкнулся с этим! но примиритель понимает, что его реквизит не изменился. Тогда никакие операции DOM не будут необходимы для этого.
  • Тот же сценарий происходит со вторым Button, это будет сравнивать по keys вместо index, Понимает, что это тот же экземпляр, и никакие реквизиты не были изменены, поэтому React решает не применять изменения в DOM.
  • Для Button с нулевым ключом, поскольку не существует дочернего элемента с таким же ключом, понимает, что экземпляр должен быть создан в VDOM, и это изменение должно быть записано в DOM.

Таким образом, использование ключей с предсказуемым содержимым помогает примирителю выполнять меньше операций с DOM. Здоровые ключи - это те, которые могут быть выведены из отображаемого объекта, например name или id или даже url если мы преобразуем urls в <imgs />,

Как насчет key=index? Не будет иметь никакого эффекта, так как по умолчанию, recciler сравнивает по позиции, то есть по индексу.

Эти ключи должны быть глобально уникальными? Не обязательно. Они должны быть уникальными среди братьев и сестер, чтобы их можно было различить при итерации по дочерним узлам.

Как насчет случайных ключей? Этого следует избегать любой ценой. Если ключ изменяется при каждом рендеринге, это будет приводить к тому, что React будет уничтожать и создавать экземпляры в VDOM (и, следовательно, делать дополнительные записи в DOM), поскольку компонент с ключом не найден среди новых дочерних элементов, но новый с тем же типом.

Если результат рендеринга

<div>
  <Button key={randomGenerator()} />
</div>

Потом каждый раз render выполняется (например, из-за изменения реквизита / состояния, или даже если его родитель перерисовывается и наш shouldComponentUpdate возвращается true), новый randomGenerator() ключ будет сгенерирован. Это будет выглядеть так:

'Привет! Я нашел Button с F67BMkd== ключ, но ни один не был найден в следующем. Я удалю это. 'Ой! Я столкнулся с Button с SHDSA++5 ключ! Давайте создадим новый ".

Всякий раз, когда посредник сообщает, что экземпляр должен быть удален и размонтирован, его внутреннее состояние будет потеряно; даже если мы смонтируем его снова. Экземпляр в VDOM не будет сохранен в этом случае.

Button было то же самое, но посредник сделал беспорядок в DOM.

Надеюсь, поможет.

Лучший способ выбрать ключ - использовать строку, которая однозначно идентифицирует элемент списка среди его братьев и сестер. Чаще всего вы используете идентификаторы из ваших данных в качестве ключей:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
);

Если у вас нет стабильных идентификаторов для отображаемых элементов, вы можете использовать индекс элемента в качестве ключа в качестве крайней меры:

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

Также обратите внимание:

Ключи, используемые в массивах, должны быть уникальными среди их братьев и сестер. Однако они не должны быть глобально уникальными.

Однако реальный ответ на ваш вопрос живет здесь: https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318

Есть много библиотек, которые генерируют случайные уникальные идентификаторы, такие как Shtitid или UUID (который является самым популярным, просто посмотрите на количество загрузок), или просто создайте свою собственную функцию, которая генерирует случайные строки.

Вы можете добавить их непосредственно в объект в массиве

const todos = [
  { 
    id: uuid(),
    text: 'foo',
  }
]

и повторить так:

const todoItems = todos.map(({id, text}) =>
  <li key={id}>
    {text}
  </li>
);

md5 sha1 или даже sha256 по содержанию.

Вы можете использовать Date.now() с индексом, ваш код будет как Ex.

const data= [{"id": "01", "name": "abc"}, {"id": "02", "name": "xyz"}];

render(){
  const items = data.map((item, i) => {
    let keyValue = Date.now()+i;
    return <span key={keyValue}>{item.name}</span>;
  }
  return(
     <div>
        {items}
     </div>
  )
}
Другие вопросы по тегам