React.js의 배열 자식에 대한 고유 키 이해


655

JSON 데이터 원본을 허용하고 정렬 가능한 테이블을 만드는 React 구성 요소를 작성 중입니다.
각 동적 데이터 행에는 고유 키가 할당되어 있지만 여전히 다음과 같은 오류가 발생합니다.

배열의 각 자식에는 고유 한 "키"소품이 있어야합니다.
TableComponent의 렌더링 방법을 확인하십시오.

TableComponent렌더 메소드는 다음을 반환합니다.

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

TableHeader구성 요소는 단일 행이며, 또한 할당 된 고유 키가 있습니다.

각 입력 rowrows고유 키가있는 구성 요소로 빌드됩니다.

<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 노드에 대한 참조를 찾고 마크 업 내의 내용 만 업데이트하지만 전체 테이블 / 행을 다시 렌더링하지 않도록 도와줍니다.
Kiril

rows배열 또는 더 바람직하게 jsfiddle을 공유 할 수 있습니까 ? 당신은 필요 없어요 key에 재산을 thead하고 tbody그런데.
nilgun

원래 질문 @nilgun에 행 구성 요소를 추가했습니다.
Brett DeWoody

3
일부 품목에 ID가 없거나 동일한 ID가있을 수 있습니까?
nilgun

답변:


621

각 하위 요소와 하위 내부의 각 요소에 키를 추가해야합니다 .

이런 식으로 React는 최소 DOM 변경을 처리 할 수 ​​있습니다.

귀하의 코드에서 각각 <TableRowItem key={item.id} data={item} columns={columnNames}/>은 키가없는 일부 어린이를 렌더링하려고합니다.

이 예를 확인하십시오 .

div 내부 key={i}<b></b>요소 에서를 제거하고 콘솔을 확인하십시오.

샘플에서, 우리는 열쇠 제공하지 않는 경우 <b>요소를 우리 업데이트 할 경우에만object.city에 요구 반작용 단지 요소 대 전체 행을 재 렌더링.

코드는 다음과 같습니다.

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에서 먼저 keyprop을 <Text>component 에 넣으려고했습니다 . 그러나 <View>부모의 구성 요소 에 넣어야했습니다 <Text>. Array == [(보기> 텍스트), (보기> 텍스트)]이면 View에 배치해야합니다. 텍스트가 아닙니다.
scaryguy

238
React가 고유 키 자체를 생성하는 것이 왜 그렇게 어려운가요?
Davor Lucic

13
@DavorLucic, 여기에 토론이 있습니다 : github.com/facebook/react/issues/1342#issuecomment-39230939
koddo

5
이것은 위의 이슈 채팅에서 작성된 메모 의 공식 단어거의 같습니다. 키는 세트 멤버의 신원과 임의의 반복자에서 나오는 항목에 대한 키의 자동 생성에 관한 것입니다. 아마도 React 내에서 성능에 영향을 미칩니다 도서관.
sameers

523

배열을 반복 할 때주의하십시오 !!

배열에서 요소의 인덱스를 사용하는 것이 익숙한 오류를 억제하는 데 허용되는 방법이라는 것은 일반적인 오해입니다.

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

그러나 많은 경우에 그렇지 않습니다! 이것은 일부 상황에서 원치 않는 동작을 유발할 수있는 안티 패턴 입니다 .


key소품 이해

React는 key소품을 사용하여 구성 요소 -DOM 요소 관계를 이해하고 조정 프로세스에 사용 됩니다 . 따라서 키가 항상 고유 한 상태를 유지 하는 것이 매우 중요합니다. 그렇지 않으면 React가 요소를 혼합하고 잘못된 요소를 변형시킬 가능성이 높습니다. 최상의 성능을 유지하려면 이러한 키 를 모든 다시 렌더링에서 정적으로 유지 하는 것이 중요합니다 .

즉 , 배열이 완전히 정적이라는 것이 알려진 경우 위의 사항을 항상 적용 할 필요 는 없습니다 . 그러나 가능하면 모범 사례를 적용하는 것이 좋습니다.

React 개발자는 이 GitHub 문제 에서 다음같이 말했습니다 .

  • 핵심은 실제로 성능에 관한 것이 아니라 정체성에 관한 것입니다 (성능 향상으로 이어집니다). 임의로 할당 및 변경되는 값은 동일하지 않습니다
  • 데이터 모델링 방식을 알지 못하면 현실적으로 키를 [자동으로] 제공 할 수 없습니다. ID가 없으면 일종의 해싱 함수를 사용하는 것이 좋습니다.
  • 배열을 사용할 때 이미 내부 키가 있지만 배열의 인덱스입니다. 새 요소를 삽입하면 해당 키가 잘못되었습니다.

즉, 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 1key={1}아니라 key={0}더 이상. 대신, Item 2이제는 key={0}!!

따라서 React <input>Itemwith 키 0가 항상 맨 위에 있기 때문에 요소가 변경되지 않았다고 생각합니다 !

그렇다면 왜이 접근법은 때때로 나쁜 것일까 요?

이 방법은 어레이가 어떻게 든 필터링, 재 배열되거나 항목이 추가 / 제거되는 경우에만 위험합니다. 항상 정적 인 경우 사용하는 것이 안전합니다. 예를 들어, ["Home", "Products", "Contact us"]새로운 링크를 추가하거나 재 배열하지 않기 때문에이 방법 으로 탐색 메뉴 를 안전하게 반복 할 수 있습니다.

간단히 말해서 다음과 같이 인덱스를 안전하게 사용할 수 있습니다 key.

  • 배열은 정적이며 변경되지 않습니다.
  • 배열은 필터링되지 않습니다 (어레이의 하위 집합 표시).
  • 배열은 다시 정렬되지 않습니다.
  • 배열은 스택 또는 LIFO (last in, first out)로 사용됩니다. 다시 말해, 추가는 배열의 끝에서만 (즉 푸시) 수행 할 수 있으며 마지막 항목 만 제거 (즉 팝) 할 수 있습니다.

대신 위의 스 니펫 에서 추가 된 항목을 배열의 끝으로 밀면 기존의 각 항목의 순서는 항상 정확합니다.


아주 나쁜

<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데이터베이스에서 가져온 데이터 가 포함 된 경우 테이블의 기본 키 ( 일반적으로 자동 증분 번호 )를 사용할 있습니다.

키를 선택하는 가장 좋은 방법은 형제 중에서 목록 항목을 고유하게 식별하는 문자열을 사용하는 것입니다. 대부분의 경우 데이터의 ID를 키로 사용합니다


좋은

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 호출 에서 또는 비동기 API 호출에서 ) 바람직 합니다. 그러한 키를 제공 할 수있는 소수의 라이브러리가 이미 있습니다. 예를 들면 다음과 같습니다. react-key-index .


1
에서 공식 문서 , 그들이 사용하는 toString()대신 문자열의 숫자로 떠나기로 변환합니다. 이것을 기억하는 것이 중요합니까?
skube

1
@skube, 아니요, 정수도 사용할 수 있습니다 key. 그들이 왜 그것을 변환하는지 확실하지 않습니다.
Chris

1
정수 사용할 는 있지만 추측 해야 합니까? 문서에 따르면 "... 키를 선택하는 가장 좋은 방법 은 고유하게 식별 하는 문자열 을 사용하는 것 입니다 ..."(강조 표시)
skube

2
@skube, 그렇습니다. 위의 예에서 언급했듯이 반복 배열의 항목 색인을 사용할 수 있습니다 (정수). 문서 상태조차 : "마지막 수단으로 배열의 항목 색인을 키로 전달할 수 있습니다 . " 무엇 불구하고 발생하는 것은이 때문이다 key항상 어쨌든 문자열 인 끝납니다.
Chris

3
@ farmcommand2에서 키는 React Components에 적용되며 형제간에 고유해야합니다 . 이것은 위에서 언급 한 것입니다. 즉, 배열에서 고유 한
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>
     )}
 )

물론 위의 예제에서 키를 반복하기 위해 순회 반복 인덱스 (인덱스)를 사용했습니다. 이상적으로는 목록 항목에 고유 한 것을 사용하는 것이 좋습니다.


매우 도움이되었습니다. 감사합니다! 나는 그것을 가장 바깥층에 놓아야한다는 것을 몰랐다
Charles Smith

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

index는 React 내장 소품입니다.


2
이 방법은 항목이 어떻게 든 재 배열 될 경우 잠재적으로 위험합니다. 그러나 그들이 정적으로 유지된다면 이것은 괜찮습니다.
Chris

이 경우 색인이 중복 될 수 있기 때문에 @chris 전적으로 귀하에게 동의합니다. 키에 동적 값을 사용하는 것이 좋습니다.
Shashank Malviya 2016 년

@ chris 또한 귀하의 의견에 동의합니다. 중복이있을 수 있으므로 인덱스 대신 동적 값을 사용해야합니다. 간단하게하기 위해이 작업을 수행했습니다. Btw 귀하의 기여에 감사드립니다 (증거)
Shashank Malviya

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

someValue가 정의되지 않았습니다 !!! 먼저 확인하십시오. 시간을 절약 할 수 있습니다.


1

반응하는 고유 키 정의의 가장 좋은 해결책 : 맵 내에서 이름 게시물을 초기화 한 다음 key = {post.id}로 키 정의를 정의하거나 내 코드에서 이름 항목을 정의한 다음 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

이것은 경고이지만, 이것을 해결하면 Reacts가 훨씬 더 빨리 렌더링됩니다 .

React목록의 각 항목을 고유하게 식별해야하기 때문 입니다. Reacts에서 해당 목록의 요소 상태가 변경되면 Virtual DOMReact가 변경된 요소와 DOM에서 변경해야하는 요소를 찾아 브라우저 DOM이 Reacts Virtual DOM과 동기화되도록해야합니다.

해결책 key으로 각 li태그에 속성을 도입하면 됩니다. 이것은 key각 요소에 고유 한 값이어야한다.


이것은 완전히 정확하지 않습니다. 렌더링 할 수 없습니다 당신이 추가하면 빠를 key소품. 하나를 제공하지 않으면 React는 하나를 자동으로 할당합니다 (반복의 현재 색인).
Chris

이 경우 @Chris 왜 경고가 발생합니까?
프라임

키를 제공하지 않으면 React는 데이터 모델링 방법을 모릅니다. 배열이 수정되면 원하지 않는 결과가 발생할 수 있습니다.
Chris

배열 수정의 경우 @Chris는 키를 제공하지 않으면 그에 따라 인덱스를 수정합니다. 어쨌든 React에서 추가 오버 헤드를 제거하면 렌더링 프로세스에 영향을 줄 것이라고 생각했습니다.
프라임

다시, React는 기본적으로 할 것 key={i}입니다. 따라서 배열에 포함 된 데이터에 따라 다릅니다. 예를 들어, 목록이 있다면 ["Volvo", "Tesla"]분명히 볼보는 키 와 Tesla로 식별 됩니다. 왜냐하면 그것이 루프에 나타나는 순서이기 때문입니다. 이제 배열을 재정렬하면 키가 교체됩니다. React의 경우 "object" 가 여전히 최상위에 있기 때문에이 변경 사항을 재정렬이 아닌 "이름 바꾸기"로 해석합니다. 올바른 키는 여기에 순서대로 할 필요가 다음 . 런타임 중 항상 재정렬하지는 않지만 그렇게하면 위험합니다. 01010
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>
    )
  }
});

이것은 문제를 해결할 것입니다.


0

<></>대신 배열의 일부 항목에 대해 반환해야 하기 때문에이 오류 메시지가 발생했습니다 null.


-1

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)를 사용하여 고유 키 또는 uniquid uuid 또는 고유 ID를 생성하지만 배열의 인덱스는 고유 ID로 생성하지 않는 다른 방법 을 설정할 수 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.