web-dev-qa-db-ja.com

React.jsの配列の子に対する一意のキーを理解する

JSONデータソースを受け入れてソート可能なテーブルを作成するReactコンポーネントを構築しています。
各動的データ行には一意のキーが割り当てられていますが、それでもエラーが発生します。 

配列内の各子には、一意の「キー」プロップが必要です。
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>
    )
  }
});

ユニークキープロップエラーの原因は何ですか? 

404
Brett DeWoody

あなたはそれぞれの子にキーを追加する必要があります と同様に子の内側の各要素

このようにしてReactは最小限のDOMの変更を処理することができます。 

あなたのコードでは、それぞれの<TableRowItem key={item.id} data={item} columns={columnNames}/>はキーなしでそれらの中の何人かの子供をレンダリングしようとしています。

この例をチェックしてください

Divの中のkey={i}要素から<b></b>を削除してみてください(そしてコンソールをチェックしてください)。

サンプルで、<b>要素にキーを与えずに only 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をご覧ください。

和解における鍵の重要性についての文書を改訂する:

402
jmingov

配列を反復するときは注意してください!!

配列内の要素のインデックスを使用することは、おそらく慣れ親しんでいるエラーを抑制する許容可能な方法であるというのは一般的な誤解です。

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

ただし、多くの場合、そうではありません!これはanti-patternであり、someの状況では望ましくない動作につながる可能性があります


keyプロップを理解する

Reactはkey propを使用してコンポーネントとDOM要素の関係を理解し​​ます。この関係は 調整プロセス に使用されます。したがって、キーalwaysniqueのままであることが非常に重要です。そうしないと、Reactが要素を混同し、誤った要素を変更する可能性が高くなります。また、最高のパフォーマンスを維持するために、すべての再レンダリングを通じてこれらのキーremain staticを使用することも重要です。

とはいえ、配列が完全に静的であることを既知である限り、上記を適用する必要はありませんalways。ただし、可能な限りベストプラクティスを適用することをお勧めします。

React開発者は このGitHubの問題

  • キーは実際にはパフォーマンスに関するものではなく、アイデンティティに関するものです(ひいてはパフォーマンスの向上につながります)。ランダムに割り当てられた値と変化する値は同一ではありません
  • データがどのようにモデル化されているかを知ることなく、現実的にキーを[自動的に]提供することはできません。 IDがない場合は、何らかのハッシュ関数を使用することをお勧めします
  • 配列を使用するときはすでに内部キーがありますが、それらは配列内のインデックスです。新しい要素を挿入すると、これらのキーは間違っています。

つまり、keyは次のようになります。

  • Unique-キーは 兄弟コンポーネント のキーと同一であってはなりません。
  • Static-レンダリング間でキーを変更しないでください。


keyプロップを使用する

上記の説明に従って、次のサンプルを慎重に検討し、可能な場合は推奨されるアプローチを実装してください。


悪い(潜在的に)

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

これはおそらく、Reactで配列を反復処理するときに見られる最も一般的な間違いです。このアプローチは技術的には"wrong"ではなく、ただ... "dangerous"何をしているのかわからない場合。静的配列を反復処理する場合、これは完全に有効なアプローチです(ナビゲーションメニューのリンクの配列など)。ただし、アイテムを追加、削除、並べ替え、またはフィルタリングする場合は、注意する必要があります。これをご覧ください 詳細な説明 公式ドキュメント。

class MyApp extends React.Component {
  constructor() {
    super();
    this.state = {
      arr: ["Item 1"]
    }
  }
  
  click = () => {
    this.setState({
      arr: ['Item ' + (this.state.arr.length+1)].concat(this.state.arr),
    });
  }
  
  render() {
    return(
      <div>
        <button onClick={this.click}>Add</button>
        <ul>
          {this.state.arr.map(
            (item, i) => <Item key={i} text={"Item " + i}>{item + " "}</Item>
          )}
        </ul>
      </div>
    );
  }
}

const Item = (props) => {
  return (
    <li>
      <label>{props.children}</label>
      <input value={props.text} />
    </li>
  );
}

ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

このスニペットでは、非静的配列を使用しており、スタックとしての使用に制限していません。これは安全でないアプローチです(理由はわかります)。配列の先頭にアイテムを追加する(基本的にシフトを解除する)ときに、各<input>の値がそのまま残ることに注意してください。どうして? keyは各アイテムを一意に識別しないためです。

つまり、最初はItem 1にはkey={0}があります。 2番目のアイテムを追加すると、一番上のアイテムはItem 2になり、2番目のアイテムとしてItem 1が続きます。ただし、Item 1key={1}ではなくkey={0}になりました。代わりに、Item 2key={0}になりました!!

そのため、Reactは、<input>要素が変更されていないと見なします。これは、キー0を持つItemが常に最上位にあるためです。

では、なぜこのアプローチだけが時々悪いのですか?

この方法は、配列が何らかの方法でフィルター処理、再配置、またはアイテムが追加/削除された場合にのみ危険です。常に静的である場合は、完全に安全に使用できます。たとえば、["Home", "Products", "Contact us"]などのナビゲーションメニューは、新しいリンクを追加したり再配置したりすることはないため、このメソッドで安全に反復処理できます。

要するに、ここで、安全にインデックスをkeyとして使用できます:

  • 配列は静的であり、変更されることはありません。
  • 配列はフィルターされません(配列のサブセットを表示します)。
  • 配列は並べ替えられません。
  • 配列は、スタックまたはLIFO(後入れ先出し)として使用されます。つまり、追加は配列の最後でのみ実行でき(プッシュなど)、最後のアイテムのみを削除(ポップ)できます。

代わりに、上記のスニペットで、配列の最後に追加されたアイテムをpushedした場合、既存の各アイテムの順序は常に正しいでしょう。


ひどい

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

このアプローチはおそらくキーの一意性を保証しますが、alwaysリスト内の各アイテムを再レンダリングするように強制します。これは必須ではありません。これはパフォーマンスに大きく影響するため、非常に悪いソリューションです。 Math.random()が同じ数を2回生成する場合、キーの衝突の可能性を排除できないことは言うまでもありません。

不安定なキー(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呼び出しから)、これを行うためだけにonceが望ましいコンポーネントが再レンダリングされるたびにではありません。そのようなキーを提供できるライブラリがすでにいくつかあります。次に例を示します。 react-key-index

383
Chris

警告:配列またはイテレータのそれぞれの子は、一意の「キー」プロップを持つべきです。

これは、私たちが繰り返し処理しようとしている配列項目については、固有の類似点が必要になるため、警告です。

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

ユニークキー をあなたのコンポーネントに追加するだけです。 

data.map((marker)=>{
    return(
        <YourComponents 
            key={data.id}     // <----- unique key
        />
    );
})
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}
              />
          ))}
1
Archil Labadze

チェック:キー= undef !!!

警告メッセージも表示されます。

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

あなたのコードが完全であれば

<ObjectRow key={someValue} />

someValueは未定義です!!!まずこれを確認してください。あなたは時間を節約することができます。

0
Gerd

私が見つけた最も簡単な解決策は、リストの各要素に対して一意のIDを作成することです。 2つのライブラリ、uniqidとuuidがあります。

npm install uniqid

それからあなたのコードで:

import uniqueid from 'uniqid'
- OR if you use require:   var uniqid = require('uniqid');

それを必要とするHTML要素にキーを追加するだけです。以下の例では、 key = {uniqueid()} に注意してください。

{this.state.someList.map((item) => <li>{item}</li>)}
I changed to this:
{this.state.someList.map((item) => <li key={uniqueid()}>{item}</li>)}
0
MattC