Hiểu các khóa duy nhất cho mảng con trong React.js


655

Tôi đang xây dựng một thành phần React chấp nhận nguồn dữ liệu JSON và tạo một bảng có thể sắp xếp.
Mỗi hàng dữ liệu động có một khóa duy nhất được gán cho nó nhưng tôi vẫn gặp lỗi:

Mỗi đứa trẻ trong một mảng nên có một "phím" duy nhất.
Kiểm tra phương thức kết xuất của TableComponent.

TableComponentPhương thức kết xuất của tôi trả về:

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

Thành TableHeaderphần này là một hàng đơn và cũng có một khóa duy nhất được gán cho nó.

Mỗi rowtrong rowsđược xây dựng từ một thành phần với một khóa duy nhất:

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

TableRowItemtrông như thế này:

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

Điều gì gây ra lỗi prop key duy nhất?


7
Các hàng của bạn trong mảng JS phải có thuộc tính duy nhất key. Nó sẽ giúp ReactJS tìm các tham chiếu đến các nút DOM thích hợp và chỉ cập nhật nội dung bên trong đánh dấu nhưng không hiển thị lại toàn bộ bảng / hàng.
Kiril

Bạn cũng có thể chia sẻ rowsmảng hoặc tốt hơn là một jsfiddle? Bạn không cần một keytài sản trên theadtbodybằng cách này.
nilgun

Tôi đã thêm thành phần hàng vào câu hỏi ban đầu @nilgun.
Brett DeWoody

3
Có thể một số mặt hàng không có id hoặc có cùng id?
nilgun 4/2/2015

Câu trả lời:


622

Bạn nên thêm một chìa khóa cho mỗi đứa trẻ cũng như từng yếu tố bên trong trẻ em .

Bằng cách này, React có thể xử lý thay đổi DOM tối thiểu.

Trong mã của bạn, mỗi người <TableRowItem key={item.id} data={item} columns={columnNames}/>đang cố gắng kết xuất một số trẻ em bên trong chúng mà không cần khóa.

Kiểm tra ví dụ này .

Hãy thử loại bỏ các key={i}từ <b></b>yếu tố bên trong div (và kiểm tra các giao diện điều khiển).

Trong mẫu, nếu chúng tôi không cung cấp chìa khóa cho <b> yếu tố và chúng tôi muốn cập nhật chỉ các object.city, Phản ứng nhu cầu để lại làm cho toàn bộ hàng vs chỉ là yếu tố.

Đây là mã:

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

Câu trả lời được đăng bởi @Chris ở phía dưới đi vào chi tiết hơn nhiều so với câu trả lời này. Vui lòng xem tại https://stackoverflow.com/a/43892905/2325522

Phản ứng tài liệu về tầm quan trọng của các khóa trong đối chiếu: Các khóa


5
Tôi đang chạy vào cùng một lỗi chính xác. Điều này đã được giải quyết sau khi trò chuyện? Nếu vậy, bạn có thể xin vui lòng gửi một bản cập nhật cho câu hỏi này.
Deke

1
Câu trả lời hoạt động, Deke. Chỉ cần đảm bảo rằng keygiá trị của prop là duy nhất cho mỗi mục và bạn đang đặt keyprop cho thành phần gần nhất với ranh giới mảng. Ví dụ, trong React Native, đầu tiên tôi đã cố gắng đưa keyprop vào <Text>thành phần. Tuy nhiên tôi đã phải đặt nó vào <View>thành phần là cha mẹ của <Text>. if Array == [(Xem> Văn bản), (Xem> Văn bản)] bạn cần đặt nó vào Chế độ xem. Không phải văn bản.
đáng sợ

240
Tại sao React lại khó tạo ra các khóa duy nhất như vậy?
Hương vị Lucic

13
@DavorLucic, đây là một cuộc thảo luận: github.com/facebook/react/issues/1342#issuecomment-39230939
koddo

5
Đây là khá nhiều từ chính thức trên một ghi chú được đưa ra trong cuộc trò chuyện được liên kết ở trên: các khóa là về danh tính của một thành viên của một tập hợp và tự động tạo các khóa cho các mục xuất hiện từ một trình vòng lặp tùy ý có thể có ý nghĩa về hiệu suất trong React thư viện.
cùng

523

Hãy cẩn thận khi lặp qua mảng !!

Một quan niệm sai lầm phổ biến rằng sử dụng chỉ mục của phần tử trong mảng là một cách có thể chấp nhận để loại bỏ lỗi mà bạn có thể quen thuộc:

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

Tuy nhiên, trong nhiều trường hợp thì không! Đây là mô hình chống có thể trong một số tình huống dẫn đến hành vi không mong muốn .


Hiểu keyprop

React sử dụng keyprop để hiểu mối quan hệ Thành phần-thành phần DOM, sau đó được sử dụng cho quá trình đối chiếu . Do đó, nó là rất quan trọng rằng chìa khóa luôn vẫn là độc đáo , nếu không có một cơ hội tốt Phản ứng sẽ trộn lẫn các yếu tố và đột biến một không chính xác. Điều quan trọng nữa là những phím này vẫn tĩnh trong tất cả các kết xuất lại để duy trì hiệu suất tốt nhất.

Điều đó đang được nói, không phải lúc nào người ta cũng cần phải áp dụng những điều trên, với điều kiện được biết rằng mảng hoàn toàn tĩnh. Tuy nhiên, áp dụng thực hành tốt nhất được khuyến khích bất cứ khi nào có thể.

Một nhà phát triển React đã nói trong vấn đề GitHub này :

  • chìa khóa không thực sự là về hiệu suất, nó là về bản sắc (điều này dẫn đến hiệu suất tốt hơn). giá trị được gán ngẫu nhiên và thay đổi không phải là danh tính
  • Chúng tôi không thể thực sự cung cấp khóa [tự động] mà không biết dữ liệu của bạn được mô hình hóa như thế nào. Tôi sẽ đề nghị có thể sử dụng một số loại hàm băm nếu bạn không có id
  • Chúng tôi đã có các khóa nội bộ khi chúng tôi sử dụng mảng, nhưng chúng là chỉ mục trong mảng. Khi bạn chèn một phần tử mới, các phím đó là sai.

Nói tóm lại, keynên là:

  • Duy nhất - Khóa không thể giống với khóa của thành phần anh chị em .
  • Tĩnh - Một khóa không bao giờ thay đổi giữa các kết xuất.


Sử dụng key chống đỡ

Theo giải thích ở trên, hãy nghiên cứu kỹ các mẫu sau và cố gắng thực hiện, khi có thể, phương pháp được khuyến nghị.


Xấu (Có khả năng)

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

Đây được cho là lỗi phổ biến nhất được thấy khi lặp qua một mảng trong React. Cách tiếp cận này không "sai" về mặt kỹ thuật , nó chỉ ... "nguy hiểm" nếu bạn không biết mình đang làm gì. Nếu bạn đang lặp qua một mảng tĩnh thì đây là một cách tiếp cận hoàn toàn hợp lệ (ví dụ: một mảng các liên kết trong menu điều hướng của bạn). Tuy nhiên, nếu bạn đang thêm, xóa, sắp xếp lại hoặc lọc các mục, thì bạn cần phải cẩn thận. Hãy xem giải thích chi tiết này trong tài liệu chính thức.

Trong đoạn mã này, chúng tôi đang sử dụng một mảng không tĩnh và chúng tôi không hạn chế sử dụng nó như một ngăn xếp. Đây là một cách tiếp cận không an toàn (bạn sẽ thấy tại sao). Lưu ý làm thế nào khi chúng ta thêm các mục vào đầu mảng (về cơ bản là không dịch chuyển), giá trị cho mỗi mục <input>vẫn giữ nguyên. Tại sao? Bởi vì keykhông xác định duy nhất từng mục.

Nói cách khác, lúc đầu Item 1key={0}. Khi chúng ta thêm mục thứ hai, mục trên cùng trở thành Item 2, tiếp theo Item 1là mục thứ hai. Tuy nhiên, bây giờ Item 1key={1}và không key={0}còn nữa. Thay vào đó, Item 2bây giờ có key={0}!!

Như vậy, React nghĩ rằng các <input>yếu tố không thay đổi, bởi vì Itemphím with 0luôn ở trên cùng!

Vậy tại sao cách tiếp cận này chỉ đôi khi xấu?

Cách tiếp cận này chỉ có rủi ro nếu mảng được lọc, sắp xếp lại hoặc các mục được thêm / xóa bằng cách nào đó. Nếu nó luôn tĩnh, thì nó hoàn toàn an toàn để sử dụng. Ví dụ: một menu điều hướng như ["Home", "Products", "Contact us"]có thể được lặp lại một cách an toàn bằng phương pháp này bởi vì có lẽ bạn sẽ không bao giờ thêm các liên kết mới hoặc sắp xếp lại chúng.

Nói tóm lại, đây là khi bạn có thể sử dụng chỉ mục một cách an toàn như keysau:

  • Các mảng là tĩnh và sẽ không bao giờ thay đổi.
  • Mảng không bao giờ được lọc (hiển thị một tập hợp con của mảng).
  • Các mảng không bao giờ được sắp xếp lại.
  • Mảng được sử dụng như một ngăn xếp hoặc LIFO (cuối cùng vào trước ra trước). Nói cách khác, việc thêm chỉ có thể được thực hiện ở cuối mảng (tức là đẩy) và chỉ có thể xóa mục cuối cùng (tức là pop).

Thay vào đó, trong đoạn trích ở trên, đã đẩy mục được thêm vào cuối mảng, thứ tự cho mỗi mục hiện có sẽ luôn chính xác.


Rất tệ

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

Mặc dù cách tiếp cận này có thể sẽ đảm bảo tính duy nhất của các phím, nhưng nó sẽ luôn buộc phản ứng để kết xuất lại từng mục trong danh sách, ngay cả khi điều này không bắt buộc. Đây là một giải pháp rất xấu vì nó ảnh hưởng lớn đến hiệu suất. Chưa kể rằng người ta không thể loại trừ khả năng xảy ra va chạm quan trọng trong trường hợp Math.random()tạo ra cùng một số hai lần.

Các khóa không ổn định (như các khóa được tạo bởi Math.random()) sẽ khiến nhiều trường hợp thành phần và các nút DOM được tạo lại một cách không cần thiết, điều này có thể làm giảm hiệu suất và mất trạng thái trong các thành phần con.


Rất tốt

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

Đây được cho là cách tiếp cận tốt nhất bởi vì nó sử dụng một thuộc tính duy nhất cho mỗi mục trong bộ dữ liệu. Ví dụ: nếu rowschứa dữ liệu được tìm nạp từ cơ sở dữ liệu, người ta có thể sử dụng Khóa chính của bảng ( thường là số tăng tự động ).

Cách tốt nhất để chọn khóa là sử dụng một chuỗi xác định duy nhất một mục danh sách giữa các anh chị em của nó. Thông thường, bạn sẽ sử dụng ID từ dữ liệu của mình làm khóa


Tốt

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

...

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

Đây cũng là một cách tiếp cận tốt. Nếu tập dữ liệu của bạn không chứa bất kỳ dữ liệu nào đảm bảo tính duy nhất ( ví dụ: một mảng các số tùy ý ), có khả năng xảy ra xung đột chính. Trong các trường hợp như vậy, tốt nhất là tạo thủ công một mã định danh duy nhất cho từng mục trong bộ dữ liệu trước khi lặp qua nó. Tốt nhất là khi gắn thành phần hoặc khi nhận được tập dữ liệu ( ví dụ: từ propshoặc từ lệnh gọi API không đồng bộ ), để chỉ thực hiện việc này một lần chứ không phải mỗi lần thành phần hiển thị lại. Hiện đã có một số thư viện có thể cung cấp cho bạn các khóa như vậy. Đây là một ví dụ: Reac-key-index .


1
Trong các tài liệu chính thức , họ sử dụng toString()để chuyển đổi thành chuỗi thay vì để lại dưới dạng số. Đây có phải là quan trọng để nhớ?
skube

1
@skube, không, bạn cũng có thể sử dụng số nguyên key. Không chắc chắn tại sao họ đang chuyển đổi nó.
Chris

1
Tôi đoán bạn có thể sử dụng số nguyên nhưng bạn nên ? Theo tài liệu của họ, họ tuyên bố "... cách tốt nhất để chọn khóa là sử dụng một chuỗi xác định duy nhất ..." (nhấn mạnh của tôi)
skube

2
@skube, vâng, điều đó là hoàn toàn chấp nhận được. Như đã nêu trong các ví dụ trên, bạn có thể sử dụng chỉ mục của mục của mảng lặp (và đó là một số nguyên). Ngay cả các tài liệu cũng nêu rõ: "Như một phương sách cuối cùng, bạn có thể chuyển chỉ mục của mục trong mảng dưới dạng khóa" . Tuy nhiên, điều xảy ra là keyluôn luôn là một Chuỗi.
Chris

3
@ farmcommand2, các khóa được áp dụng cho các Thành phần Phản ứng và cần phải là duy nhất giữa các anh chị em . Điều này được nêu ở trên. Nói cách khác, duy nhất trong mảng
Chris

8

Điều này có thể hoặc không giúp được ai đó, nhưng nó có thể là một tài liệu tham khảo nhanh. Điều này cũng tương tự như tất cả các câu trả lời được trình bày ở trên.

Tôi có rất nhiều vị trí tạo danh sách bằng cấu trúc bên dưới:

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

Sau một chút thử nghiệm và lỗi (và một số thất vọng), việc thêm một thuộc tính quan trọng vào khối ngoài cùng đã giải quyết nó. Cũng lưu ý rằng thẻ <> hiện được thay thế bằng thẻ ngay bây giờ.

return (

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

Tất nhiên, tôi đã ngây thơ sử dụng chỉ số lặp (chỉ mục) để điền giá trị khóa trong ví dụ trên. Lý tưởng nhất là bạn sử dụng thứ gì đó duy nhất cho mục danh sách.


Điều này là vô cùng hữu ích, cảm ơn bạn! Tôi thậm chí không nhận ra mình phải đặt nó ở lớp ngoài cùng
Charles Smith

6

Cảnh báo: Mỗi đứa trẻ trong một mảng hoặc iterator nên có một "phím" duy nhất.

Đây là một cảnh báo vì đối với các mục mảng mà chúng ta sẽ lặp đi lặp lại sẽ cần một sự tương đồng duy nhất.

React xử lý lặp lại kết xuất thành phần dưới dạng mảng.

Cách tốt hơn để giải quyết vấn đề này là cung cấp chỉ mục cho các mục mảng bạn sẽ lặp đi lặp lại. Ví dụ:

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

chỉ số là React tích hợp đạo cụ.


2
Cách tiếp cận này có khả năng nguy hiểm nếu các mục được sắp xếp lại bằng cách nào đó. Nhưng nếu họ vẫn tĩnh thì điều này là tốt.
Chris

@chris Tôi hoàn toàn đồng ý với bạn vì trong trường hợp này chỉ số có thể bị trùng lặp. Tốt hơn để sử dụng các giá trị động cho khóa.
Shashank Malviya

@chris Tôi cũng đồng ý với nhận xét của bạn. Chúng ta nên sử dụng các giá trị động hơn là lập chỉ mục vì có thể có các bản sao. Để làm cho nó đơn giản, tôi đã làm điều này. Btw cảm ơn sự đóng góp của bạn (được nâng cấp)
Shashank Malviya

6

Chỉ cần thêm khóa duy nhất vào Thành phần của bạn

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

6

Kiểm tra: key = undef !!!

Bạn cũng nhận được thông báo cảnh báo:

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

nếu mã của bạn hoàn thành đúng, nhưng nếu trên

<ObjectRow key={someValue} />

someValue không xác định !!! Vui lòng kiểm tra điều này đầu tiên. Bạn có thể tiết kiệm hàng giờ.


1

Giải pháp tốt nhất để xác định khóa duy nhất trong phản ứng: bên trong bản đồ bạn đã khởi tạo bài đăng tên sau đó khóa xác định bởi key = {post.id} hoặc trong mã của tôi, bạn thấy tôi xác định mục tên sau đó tôi xác định khóa theo key = {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

Đây là một cảnh báo, nhưng việc giải quyết vấn đề này sẽ khiến Reacts hiển thị NHANH CHÓNG hơn ,

Đây là vì React cần xác định duy nhất từng mục trong danh sách. Hãy nói rằng nếu trạng thái của một phần tử trong danh sách đó thay đổi trong Reacts Virtual DOMthì React cần tìm ra phần tử nào đã thay đổi và trong DOM cần thay đổi để DOM trình duyệt sẽ đồng bộ với DOM Virtual Reacts.

Như một giải pháp chỉ cần giới thiệu một keythuộc tính cho mỗi lithẻ. Đây keyphải là một giá trị duy nhất cho mỗi yếu tố.


Đây không phải là hoàn toàn chính xác. Kết xuất sẽ không nhanh hơn nếu bạn thêm keyprop. Nếu bạn không cung cấp một, React sẽ tự động gán một (chỉ mục hiện tại của lần lặp).
Chris

@Chris trong trường hợp đó tại sao nó đưa ra một cảnh báo?
nguyên tố

bởi vì không cung cấp khóa, React không biết dữ liệu của bạn được mô hình hóa như thế nào. Điều này có thể dẫn đến kết quả không mong muốn nếu mảng được sửa đổi.
Chris

@Chris trong trường hợp sửa đổi mảng đó, sẽ Phản ứng sửa các chỉ số theo đó nếu chúng tôi không cung cấp khóa. Dù sao, tôi nghĩ rằng việc loại bỏ thêm chi phí từ React sẽ ảnh hưởng đến quá trình kết xuất.
nguyên tố

một lần nữa, React về cơ bản sẽ làm key={i}. Vì vậy, nó phụ thuộc vào dữ liệu mảng của bạn chứa. Ví dụ: nếu bạn có danh sách ["Volvo", "Tesla"], thì rõ ràng Volvo được xác định bởi khóa 0và Tesla với 1- bởi vì đó là thứ tự chúng sẽ xuất hiện trong vòng lặp. Bây giờ nếu bạn sắp xếp lại mảng, các phím được hoán đổi. Đối với React, vì "đối tượng"0 vẫn ở trên cùng, nên thay vào đó sẽ diễn giải sự thay đổi này là "đổi tên", thay vì sắp xếp lại. Các phím đúng ở đây sẽ cần phải, theo thứ tự, 1sau đó 0. Bạn không luôn luôn sắp xếp lại thời gian chạy giữa chừng, nhưng khi bạn làm vậy, đó là một rủi ro.
Chris

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

Điều này sẽ khắc phục vấn đề.


0

Tôi đã chạy vào thông báo lỗi này vì <></>được trả lại cho một số mục trong mảng khi thay vào đó nullcần phải trả lại.


-1

Tôi đã sửa lỗi này bằng cách sử dụng Hướng dẫn cho từng khóa như thế này: Tạo Hướng dẫn:

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

Và sau đó gán giá trị này cho các điểm đánh dấu:

{this.state.markers.map(marker => (
              <MapView.Marker
                  key={this.guid()}
                  coordinate={marker.coordinates}
                  title={marker.title}
              />
          ))}

2
Thêm một số ngẫu nhiên làm chìa khóa là có hại! Nó sẽ ngăn phản ứng để phát hiện các thay đổi, đó là mục đích của chìa khóa.
pieaveragy

-1

Về cơ bản, bạn không nên sử dụng chỉ mục của mảng làm khóa duy nhất. Thay vào đó, bạn có thể sử dụng a setTimeout(() => Date.now(),0)để đặt khóa duy nhất hoặc uuid không thanh khoản hoặc bất kỳ cách nào khác sẽ tạo id duy nhất nhưng không phải là chỉ mục của mảng là id duy nhất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.