Розуміння унікальних ключів для дітей масиву в React.js


655

Я будую компонент 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.
Brett DeWoody

3
Чи можливо, що деякі елементи не мають ідентифікатора чи мають такий самий ідентифікатор?
nilgun

Відповіді:


621

Ви повинні додати ключ до кожної дитини , а також до кожного елемента всередині дітей .

Таким чином React може впоратися з мінімальною зміною DOM.

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

Перевірте цей приклад .

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

У зразку, якщо ми не надаємо ключ <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

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


5
Я зіткнувся з точно такою ж помилкою. Чи було це вирішено після чату? Якщо так, то можете, будь ласка, опублікувати оновлення цього питання.
Deke

1
Відповідь працює, Деке. Просто переконайтеся, що keyзначення для реквізиту є унікальним для кожного елемента, і ви ставите keyреквізит до компонента, найближчого до меж масиву. Наприклад, в React Native, спершу я намагався поставити keyопору на <Text>компонент. Однак я повинен був поставити його на <View>компонент, який є батьківським <Text>. якщо масив == [(Перегляд> Текст), (Перегляд> Текст)] потрібно помістити його в режим Перегляд. Не текст.
scaryguy

238
Чому React так важко створювати унікальні ключі самостійно?
Давор Лучич

13
@DavorLucic, ось дискусія: github.com/facebook/react/isissue/1342#issuecomment-39230939
koddo

5
Це в значній мірі офіційне слово на замітці, зробленій у чаті випуску, пов'язаному вище: ключі - це ідентифікація члена набору, а автоматичне генерація ключів для елементів, що виходять з довільного ітератора, ймовірно, має наслідки для продуктивності в рамках React бібліотека.
самісінь

523

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

Поширена помилкова думка, що використання індексу елемента в масиві є прийнятним способом придушення помилки, з якою ви, мабуть, знайомі:

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

Однак у багатьох випадках це не так! Це анти-модель, яка в деяких ситуаціях може призвести до небажаної поведінки .


Розуміння keyопори

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

Незважаючи на це, не завжди потрібно застосовувати вище, якщо відомо, що масив повністю статичний. Однак застосування кращих практик рекомендується, коли це можливо.

У цьому випуску GitHub розробник React сказав :

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

Якщо коротко, a 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клавіша 0завжди вгорі!

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

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

Коротше кажучи, ось коли ви можете сміливо використовувати індекс як key:

  • Масив статичний і ніколи не зміниться.
  • Масив ніколи не фільтрується (відображається підмножина масиву).
  • Масив ніколи не впорядковується.
  • Масив використовується як стек або LIFO (останній у, перший вихід). Іншими словами, додавання може бути здійснено лише в кінці масиву (тобто натискання), і тільки останній елемент можна буде коли-небудь видалити (тобто поп).

Якби ми, замість цього, у фрагменті вище натиснули доданий елемент до кінця масиву, порядок для кожного існуючого елемента завжди був би правильним.


Дуже погано

<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завжди все-таки стає струною.
Кріс

3
@ farmcommand2, ключі застосовуються до React Components і повинні бути унікальними серед братів і сестер . Про це сказано вище. Іншими словами, унікальний у масиві
Кріс

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>
     )}
 )

Звичайно, я наївно використовував ітераційний індекс (індекс) для заповнення ключового значення у наведеному вище прикладі. В ідеалі ви б використовували щось унікальне для елемента списку.


Це було надзвичайно корисно, дякую! Я навіть не здогадувався про те, що мені потрібно було нанести його в найвіддаленіший шар
Чарльз Сміт,

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
Такий підхід потенційно небезпечний, якщо предмети якимось чином переставити. Але якщо вони залишаться статичними, то це добре.
Кріс,

@chris Я повністю згоден з вами, тому що в цьому випадку індекс може бути дубльований. Краще використовувати динамічні значення для ключа.
Шашанк Мальвія

@chris Я також згоден з вашим коментарем. Ми повинні використовувати динамічні значення, а не індексувати, оскільки можуть бути дублікати. Щоб зробити це просто, я це зробив. До речі, дякую за ваш внесок (оновлено)
Мальвія

6

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

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})

6

Перевірка: ключ = undef !!!

Ви також отримали попередження:

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

якщо ваш код є повністю правильним, але якщо він увімкнено

<ObjectRow key={someValue} />

деякаValue не визначена !!! Перевірте це спочатку. Ви можете заощадити години.


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

Це попередження, але вирішення цього питання зробить Reacts набагато швидшим ,

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

Як рішення просто введіть keyатрибут до кожного liтегу. Це keyмає бути унікальним значенням для кожного елемента.


Це не зовсім правильно. Відображення не буде швидшим, якщо ви додасте keyопору. Якщо ви не надаєте його, React призначить його автоматично (поточний індекс ітерації).
Кріс,

@Chris у такому випадку чому він викликає попередження?
прем'єр

оскільки, не надаючи ключ, 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 або будь-яким іншим способом, який генеруватиме унікальний ідентифікатор, але не індекс масиву як унікальний ідентифікатор.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.