Понимание уникальных ключей для дочерних массивов в React.js

658

Я создаю компонент React, который принимает источник данных JSON и создает сортируемую таблицу.
Каждой из строк динамических данных присвоен уникальный ключ, но я все еще получаю сообщение об ошибке:

Каждый дочерний элемент в массиве должен иметь уникальную «ключевую» опору.
Проверьте метод визуализации TableComponent.

Мой TableComponentметод рендеринга возвращает:

<table>
  <thead key="thead">
    <TableHeader columns={columnNames}/>
  </thead>
  <tbody key="tbody">
    { rows }
  </tbody>
</table>

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

Каждый rowв rowsпостроен из компонента с уникальным ключом:

<TableRowItem key={item.id} data={item} columns={columnNames}/>

И TableRowItemвыглядит так:

var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c) {
          return <td key={this.props.data[c]}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

В чем причина ошибки уникального ключа?

Бретт ДеВуди
источник
7
Ваши строки в массиве JS должны иметь уникальное keyсвойство. Это поможет ReactJS найти ссылки на соответствующие узлы DOM и обновить только содержимое внутри разметки, но не перерисовывать всю таблицу / строку.
Кирилл
Вы также можете поделиться rowsмассивом или более предпочтительно jsfiddle? Вам не нужно keyсвойство на theadи tbody, кстати.
nilgun
Я добавил компонент строки в исходный вопрос @nilgun.
Бретт ДеВуди,
3
Возможно ли, что некоторые предметы не имеют идентификатора или имеют одинаковый идентификатор?
nilgun

Ответы:

624

Вы должны добавить ключ к каждому дочернему элементу, а также к каждому элементу внутри дочерних элементов .

Таким образом, React может справиться с минимальным изменением DOM.

В вашем коде каждый <TableRowItem key={item.id} data={item} columns={columnNames}/>пытается представить некоторых детей внутри себя без ключа.

Проверьте этот пример .

Попробуйте удалить элемент key={i}из <b></b>элемента div (и проверьте консоль).

В образце, если мы не даем ключ к <b>элементу , и мы хотим обновить толькоobject.city , React потребности повторной визуализации всю строку против всего элемента.

Вот код:

var data = [{name:'Jhon', age:28, city:'HO'},
            {name:'Onhj', age:82, city:'HN'},
            {name:'Nohj', age:41, city:'IT'}
           ];

var Hello = React.createClass({

    render: function() {

      var _data = this.props.info;
      console.log(_data);
      return(
        <div>
            {_data.map(function(object, i){
               return <div className={"row"} key={i}> 
                          {[ object.name ,
                             // remove the key
                             <b className="fosfo" key={i}> {object.city} </b> , 
                             object.age
                          ]}
                      </div>; 
             })}
        </div>
       );
    }
});

React.render(<Hello info={data} />, document.body);

Ответ, размещенный @Chris внизу, более детален, чем этот ответ. Пожалуйста, посмотрите на https://stackoverflow.com/a/43892905/2325522

Реагировать на документацию о важности ключей в сверке: ключи

jmingov
источник
5
Я сталкиваюсь с точно такой же ошибкой. Это было решено после чата? Если да, можете ли вы опубликовать обновление этого вопроса.
Декабрь
1
Ответ работает, Deke. Просто убедитесь, что keyзначение для подпорки уникально для каждого элемента, и вы добавляете keyподпорку к компоненту, ближайшему к границам массива. Например, в React Native сначала я пытался поместить keyопору в <Text>компонент. Однако мне пришлось поместить его в <View>компонент, который является родителем <Text>. если Array == [(View> Text), (View> Text)], вам нужно поместить его в View. Не текст.
Страшно
240
Почему React так сложно самому генерировать уникальные ключи?
Давор Лючич
13
@DavorLucic, вот обсуждение: github.com/facebook/react/issues/1342#issuecomment-39230939
koddo
5
Это в значительной степени официальное слово в заметке, сделанной в проблемном чате, связанном с вышеупомянутым: ключи относятся к идентификатору члена набора, а автоматическая генерация ключей для элементов, появляющихся из произвольного итератора, вероятно, влияет на производительность в React библиотека.
Sameers
525

Будьте осторожны при переборе массивов !!

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

Each child in an array should have a unique "key" prop.

Однако во многих случаях это не так! Это анти-паттерн, который в некоторых ситуациях может привести к нежелательному поведению .


Понимание keyопоры

React использует keyреквизит для понимания отношения компонента к элементу DOM, которое затем используется для процесса согласования . Поэтому очень важно, чтобы ключ всегда оставался уникальным , иначе есть хороший шанс, что React смешает элементы и изменит неправильный. Также важно, чтобы эти ключи оставались статичными течение всех повторных рендеров, чтобы поддерживать наилучшую производительность.

Это сказанное, не всегда нужно применять вышеупомянутое, если известно, что массив полностью статичен. Тем не менее, по возможности рекомендуется применять лучшие практики.

Разработчик React сказал в этом выпуске GitHub :

  • ключ на самом деле не в производительности, а скорее в идентичности (что, в свою очередь, ведет к повышению производительности). случайно назначенные и изменяющиеся значения не идентичны
  • Мы не можем реально предоставить ключи [автоматически], не зная, как моделируются ваши данные. Я хотел бы предложить, возможно, использовать какую-то функцию хеширования, если у вас нет идентификаторов
  • У нас уже есть внутренние ключи, когда мы используем массивы, но они являются индексом в массиве. Когда вы вставляете новый элемент, эти ключи неверны.

Короче говоря, keyдолжно быть:

  • Уникальный - ключ не может быть идентичным ключу родственного компонента .
  • Статический - ключ не должен меняться между рендерами.


С использованием key опору

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


Плохо (Потенциально)

<tbody>
    {rows.map((row, i) => {
        return <ObjectRow key={i} />;
    })}
</tbody>

Возможно, это самая распространенная ошибка, возникающая при переборе массива в React. Этот подход технически не «неправильный» , он просто… «опасный», если вы не знаете, что делаете. Если вы перебираете статический массив, то это совершенно правильный подход (например, массив ссылок в вашем меню навигации). Однако, если вы добавляете, удаляете, переупорядочиваете или фильтруете элементы, вам следует быть осторожным. Взгляните на это подробное объяснение в официальной документации.

В этом фрагменте мы используем нестатический массив и не ограничиваемся его использованием в качестве стека. Это небезопасный подход (вы поймете почему). Обратите внимание, как при добавлении элементов в начало массива (в основном без смещения) значение каждого из них <input>остается на месте. Почему? Потому что keyне уникально идентифицирует каждый элемент.

Другими словами, сначала Item 1имеет key={0}. Когда мы добавим второй элемент, верхний элемент становится Item 2, а затем в Item 1качестве второго пункта. Однако сейчас Item 1есть key={1}и не key={0}больше. Вместо этого Item 2теперь есть key={0}!!

Таким образом, React считает, что <input>элементы не изменились, потому что Itemключ with 0всегда находится вверху!

Так почему такой подход только иногда плох?

Такой подход опасен только в том случае, если массив каким-то образом фильтруется, переставляется или элементы добавляются / удаляются. Если он всегда статичен, то использовать его совершенно безопасно. Например, с ["Home", "Products", "Contact us"]помощью этого метода можно безопасно перемещаться по меню навигации, например, так как вы, вероятно, никогда не добавите новые ссылки и не переставите их.

Короче говоря, вот когда вы можете безопасно использовать индекс как key:

  • Массив статичен и никогда не изменится.
  • Массив никогда не фильтруется (отображать подмножество массива).
  • Массив никогда не переупорядочивается.
  • Массив используется как стек или LIFO (последний вошел, первый вышел). Другими словами, добавление может быть сделано только в конце массива (т. Е. Push), и только последний элемент может быть удален (т. Е. Pop).

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


Очень плохо

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={Math.random()} />;
    })}
</tbody>

Хотя этот подход, вероятно, гарантирует уникальность ключей, он всегда заставляет реагировать на повторную визуализацию каждого элемента в списке, даже если это не требуется. Это очень плохое решение, так как оно сильно влияет на производительность. Не говоря уже о том, что нельзя исключать возможность столкновения клавиш в том случае, Math.random()если дважды получается одно и то же число.

Нестабильные ключи (наподобие тех, что созданы Math.random()) приведут к ненужному воссозданию многих экземпляров компонентов и узлов DOM, что может привести к снижению производительности и потере состояния в дочерних компонентах.


Отлично

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uniqueId} />;
    })}
</tbody>

Это, пожалуй, лучший подход, поскольку он использует свойство, уникальное для каждого элемента в наборе данных. Например, если rowsсодержит данные, извлеченные из базы данных, можно использовать первичный ключ таблицы ( который обычно представляет собой число с автоинкрементом ).

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


Хорошо

componentWillMount() {
  let rows = this.props.rows.map(item => { 
    return {uid: SomeLibrary.generateUniqueID(), value: item};
  });
}

...

<tbody>
    {rows.map((row) => {
        return <ObjectRow key={row.uid} />;
    })}
</tbody>

Это тоже хороший подход. Если ваш набор данных не содержит данных, которые гарантируют уникальность ( например, массив произвольных чисел ), существует вероятность столкновения ключа. В таких случаях лучше всего вручную генерировать уникальный идентификатор для каждого элемента в наборе данных, прежде чем выполнять его итерацию. Предпочтительно при монтировании компонента или при получении набора данных ( например, от propsили из асинхронного вызова API ), чтобы сделать это только один раз , а не каждый раз, когда компонент повторно выполняет рендеринг. Уже есть несколько библиотек, которые могут предоставить вам такие ключи. Вот один пример: реакция-ключ-индекс .

Крис
источник
1
В официальных документах они используются toString()для преобразования в строку, а не в качестве числа. Это важно помнить?
skube
1
@skube, нет, вы также можете использовать целые числа key. Не уверен, почему они конвертируют это.
Крис
1
Я думаю, вы можете использовать целые числа, но не так ли? Согласно их документам, они утверждают, что «... лучший способ выбрать ключ - это использовать строку, которая однозначно идентифицирует ...» (выделено мной)
skube
2
@skube, да, это вполне приемлемо. Как указано в приведенных выше примерах, вы можете использовать индекс элемента итеративного массива (и это целое число). Даже в документации говорится: «В крайнем случае, вы можете передать индекс элемента в массиве в качестве ключа» . Но что происходит, так это то, что в keyлюбом случае всегда получается String.
Крис
3
@ farmcommand2, ключи применяются к компонентам React и должны быть уникальными среди братьев и сестер . Это указано выше. Другими словами, уникальный в массиве
Chris
8

Это может или не может помочь кому-то, но это может быть краткий справочник. Это также похоже на все ответы, представленные выше.

У меня есть много мест, которые генерируют список, используя структуру ниже:

return (
    {myList.map(item => (
       <>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </>
     )}
 )

После небольшой проб и ошибок (и некоторых разочарований) добавление ключевого свойства к внешнему блоку разрешило его. Также обратите внимание, что тег <> теперь заменен тегом.

return (

    {myList.map((item, index) => (
       <div key={index}>
          <div class="some class"> 
             {item.someProperty} 
              ....
          </div>
       </div>
     )}
 )

Конечно, я наивно использовал итерационный индекс (index) для заполнения значения ключа в приведенном выше примере. В идеале вы должны использовать что-то уникальное для элемента списка.

apil.tamang
источник
Это было очень полезно, спасибо! Я даже не осознавал, что должен был поместить его в самый внешний слой
Чарльз Смит,
6

Предупреждение: каждый дочерний элемент в массиве или итераторе должен иметь уникальную «ключевую» опору.

Это предупреждение, поскольку для элементов массива, которые мы собираемся перебрать, потребуется уникальное сходство.

React обрабатывает рендеринг компонентов в виде массивов.

Лучший способ решить эту проблему - предоставить индекс для элементов массива, которые вы собираетесь перебирать. Например:

class UsersState extends Component
    {
        state = {
            users: [
                {name:"shashank", age:20},
                {name:"vardan", age:30},
                {name:"somya", age:40}
            ]
        }
    render()
        {
            return(
                    <div>
                        {
                            this.state.users.map((user, index)=>{
                                return <UserState key={index} age={user.age}>{user.name}</UserState>
                            })
                        }
                    </div>
                )
        }

индекс React встроенный реквизит.

Шашанк Мальвия
источник
2
Такой подход потенциально опасен, если элементы каким-либо образом переставляются. Но если они остаются статичными, то это нормально.
Крис
@ Крис Я полностью согласен с вами, потому что в этом случае индекс может дублироваться. Лучше использовать динамические значения для ключа.
Шашанк Мальвия
@ Крис Я тоже согласен с вашим комментарием. Мы должны использовать динамические значения, а не индексировать, потому что могут быть дубликаты. Для простоты я сделал это. Кстати, спасибо за ваш вклад (голосование одобрено)
Шашанк Мальвия
6

Просто добавьте уникальный ключ к своим компонентам

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})
Bashirpour
источник
6

Проверьте: ключ = undef !!!

Вы также получили предупреждение:

Each child in a list should have a unique "key" prop.

если ваш код завершен правильно, но если на

<ObjectRow key={someValue} />

someValue не определено !!! Пожалуйста, проверьте это сначала. Вы можете сэкономить часы.

Герд
источник
1

Лучшее решение для определения уникального ключа в реакции: внутри карты вы инициализировали имя поста, затем ключ определяете по ключу = {post.id} или в моем коде вы видите, что я определяю имя элемента, а затем я определяю ключ по ключу = {item.id }:

<div className="container">
                {posts.map(item =>(

                    <div className="card border-primary mb-3" key={item.id}>
                        <div className="card-header">{item.name}</div>
                    <div className="card-body" >
                <h4 className="card-title">{item.username}</h4>
                <p className="card-text">{item.email}</p>
                    </div>
                  </div>
                ))}
            </div>

Хадим Рана
источник
0

Это предупреждение, но решение этой проблемы сделает рендеринг намного быстрее ,

Это потому, что Reactнеобходимо однозначно идентифицировать каждый элемент в списке. Скажем, если состояние элемента этого списка изменяется в ReactsVirtual DOM то React должен выяснить, какой элемент был изменен и где в DOM его нужно изменить, чтобы DOM браузера синхронизировался с Reacts Virtual DOM.

В качестве решения просто введите keyатрибут для каждого liтега. Это keyдолжно быть уникальным значением для каждого элемента.

простое число
источник
Это не совсем правильно. Рендеринг не будет быстрее, если вы добавите keyопору. Если вы его не предоставите, React назначит его автоматически (текущий индекс итерации).
Крис
@ Крис в таком случае, почему это вызывает предупреждение?
премьер
потому что, не предоставляя ключ, React не знает, как моделируются ваши данные. Это может привести к нежелательным результатам, если массив будет изменен.
Крис
@Chris в этом случае модификации массива, будет реагировать на исправление индексов в соответствии с этим, если мы не предоставили ключи. В любом случае я думал, что удаление дополнительных издержек из React окажет некоторое влияние на процесс рендеринга.
премьер
опять же, React будет в основном делать key={i}. Так что это зависит от данных, которые содержит ваш массив. Например, если у вас есть список ["Volvo", "Tesla"], то, очевидно, Volvo идентифицируется по ключу, 0а Tesla 1- потому, что в этом порядке они будут отображаться в цикле. Теперь, если вы измените порядок массива, ключи поменяются местами. Для React, поскольку «объект» 0все еще находится наверху, он скорее будет интерпретировать это изменение как «переименование», а не как переупорядочение. Правильные ключи здесь должны были бы быть в порядке, а 1затем 0. Вы не всегда меняете порядок во время выполнения, но когда это происходит, это риск.
Крис
0
var TableRowItem = React.createClass({
  render: function() {

    var td = function() {
        return this.props.columns.map(function(c, i) {
          return <td key={i}>{this.props.data[c]}</td>;
        }, this);
      }.bind(this);

    return (
      <tr>{ td(this.props.item) }</tr>
    )
  }
});

Это решит проблему.

Рамеш
источник
0

Я столкнулся с этим сообщением об ошибке, потому что <></>его возвращали для некоторых элементов в массиве, когда вместо этого nullнужно было возвращать.

Фелипе
источник
-1

Я исправил это с помощью Guid для каждого ключа следующим образом: Генерация Guid:

guid() {
    return this.s4() + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' +
        this.s4() + '-' + this.s4() + this.s4() + this.s4();
}

s4() {
    return Math.floor((1 + Math.random()) * 0x10000)
        .toString(16)
        .substring(1);
}

И затем присвоение этого значения маркерам:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}
Арчил Лабадзе
источник
2
Добавление случайного числа в качестве ключа вредно! Это предотвратит реакцию на обнаружение изменений, что является целью ключа.
pihentagy
-1

По сути, вы не должны использовать индекс массива в качестве уникального ключа. Скорее вы можете использовать a setTimeout(() => Date.now(),0)для установки уникального ключа или уникального uuid или любым другим способом, который будет генерировать уникальный идентификатор, но не индекс массива в качестве уникального идентификатора.

Рохан Найк
источник