Reactキーの仕組みと、キーを使ってできる楽しいこと

Reactは、key調整フェーズで属性を使用して、次のレンダリングで再利用できる要素を決定します。それらは動的リストにとって重要です。Reactは、新しい要素のキーを以前のキーと比較し、1)新しいキーを持つコンポーネントをマウントします。2)キーが使用されなくなったコンポーネントをマウント解除します。

多くのReact開発者はindex、キーとして使用すべきではないという一般的なアドバイスを聞いています。しかし、keysを悪い方法で使用すると、正確に何がうまくいかない可能性がありますか?キーをいじるとき、他に何ができるでしょうか?

理解を深めるために、のリストをレンダリングする例を考えてみましょうinput。ボタンをクリックすると、リストのFront先頭にテキスト付きの新しいアイテムが挿入されます。

import React from "react";import { render } from "react-dom";class Item extends React.PureComponent { state = { text: this.props.text }; onChange = event => { this.setState({ text: event.target.value }); }; componentDidMount() { console.log("Mounted ", this.props.text); } componentWillUnmount() { console.log("Unmounting ", this.props.text); } render() { console.log("rerendering ", this.props.text); const { text } = this.state; return ( 
  • ); }}class App extends React.Component { state = { items: [ { text: "First", id: 1 }, { text: "Second", id: 2 } ] }; addItem = () => { const items = [{ text: "Front", id: Date.now() }, ...this.state.items]; this.setState({ items }); }; render() { return (
      {this.state.items.map((item, index) => ( ))}
    Add Item ); }}render(, document.getElementById("root"));

    indexキーとして使用する場合、次のことが起こります。

    CodeSandbox

    CodeSandboxは、Webアプリケーション用に調整されたオンラインエディターです。codesandbox.io

    リストのItem最後にSecond代わりにテキストを含む別のものFrontが挿入された場合はどうなりますか?何が起こるかです:

    1. Item is an uncontrolled component:ユーザーがinputフィールドに書き込んだテキストは、次のように保存されます。state
    2. { text: "Front" }リストデータの先頭に新しいデータ項目が挿入されます。
    3. リストは、インデックス値をとして再レンダリングされkeyます。したがって、前のコンポーネントは最初の2つのデータ項目に再利用され、正しい小道具Frontとが与えられますFirstが、状態はで更新されませんItem。そのため、最初の2つのコンポーネントインスタンスは同じテキストを保持します。
    4. key: 2以前に一致するキーが見つからないため、新しいコンポーネントインスタンスが作成されます。props最後のリストデータ項目のでいっぱいですSecond

    もう1つの興味深い点は、render発生する呼び出しです。アイテムはであるPureComponentため、text小道具(または状態)が変更された場合にのみ更新されます。

    rerendering Frontrerendering Firstrerendering SecondMounted Second

    すべてのコンポーネントが再レンダリングされます。これは、の要素がkey: 0最初のデータアイテムに再利用されてそのを受け取るためpropsに発生しますが、最初のデータアイテムは新しいFrontオブジェクトになり、をトリガーしrenderます。古いデータ項目がすべて1つの場所にシフトされているため、他のコンポーネントでも同じことが起こります。

    それで、修正は何ですか?修正は簡単です。作成id各リストデータアイテムに一意の1回を与えます(各レンダリングではありません!)。すべてのコンポーネントインスタンスは、対応するデータ項目と照合されます。彼らはprops以前と同じように受け取ります、そしてこれは別のものを避けrenderます。

    id今のところ、動的リストでsを使用することによるパフォーマンス上の利点は無視しましょう。この例は、キーによって導入されバグが、制御されていないコンポーネント、つまり内部状態を保持するコンポーネントに関してのみ発生することを示しています

    Item制御されたコンポーネントとして書き直すと、状態をそのコンポーネントから移動することで、バグはなくなります。

    どうして?繰り返しますが、バグが別のデータ項目のコンポーネントを再利用していたためです。したがって、内部状態は前のデータ項目の状態を反映していますが、別のデータ項目小道具を反映しています。コンポーネントを制御し、その状態を完全に削除することで、この不一致はなくなりました。(ただし、不要な再レンダリングにはまだ問題があります。)

    壊れたサードパーティコンポーネントを修正するためのキーの悪用

    Reactはkey、複数の要素を照合する場合にのみsを必要とするため、1つの子にキーを設定する必要はありません。ただし、単一の子コンポーネントにキーを設定すると便利な場合があります。

    キーを変更すると、Reactはコンポーネント全体を破棄し(マウントを解除)、その場所に新しいコンポーネントインスタンスをマウントします。なぜこれが役立つのでしょうか?

    繰り返しになりますが、制御されていないコンポーネントに戻ります。サードパーティのコンポーネントを使用していて、そのコードを変更して制御できない場合があります。コンポーネントに内部状態があり、それが不適切な方法で実装されている場合(たとえば、状態はコンストラクターで1回だけ導出されますが、getDerivedStateFromProps/componentWillReceiveProps内部状態で繰り返し発生propsする変更反映するように実装されいません、標準のReactツールボックスは役に立ちませんここに。ありませんforceRemount

    ただし、keyこのコンポーネントにnewを設定するだけで、新しいコンポーネントを完全に初期化するという望ましい動作を実現できます。古いコンポーネントはマウント解除され、新しいコンポーネントは新しいprops初期化でマウントされますstate

    TL; DR:

    Using index as a key can:

    1. lead to unnecessary re-renders
    2. introduce bugs when the list items are uncontrolled components but still use props

    The key property can be used to force a complete remount of a component, which can sometimes be useful.

    Originally published at cmichel.io