Help us understand the problem. What is going on with this article?

Reactで繰り返し項目の独立性を高めるとkeyの設定に悩む

More than 1 year has passed since last update.

概要

配列をもとにしてこんなビューを作りたいときに試行錯誤してコンポーネントの独立性を高められたけれどkeyの設定に悩んでいる、という話です。

<ul>
  <li><span>#1 foo</span></li>
  <li><span>#2 bar</span></li>
  <li><span>#3 baz</span></li>
</ul>

どういう経緯でkeyの悩みに至ったか段階的に書いていきます。

以降に出てくるコード例の前提

出てくるコンポーネントはRootParentChildの3つです。
構造はこんな感じ。

<Root>
  <Parent>
    <Child><!-- ここが配列をもとに繰り返される -->
  </Parent>
</Root>

使うデータはこんな感じ。

class Content {
    constructor(id, text) {
        this.id = id;
        this.text = text;
    }
}

const contents = [
    new Content(1, 'foo'),
    new Content(2, 'bar'),
    new Content(3, 'baz')
];

Step.1 まずは何も考えずに書いてみた

const Root = ({ contents }) => <Parent contents={contents} />;

const Parent = ({ contents }) => <ul>
      {contents.map(x => <Child key={x.id} content={x}/>)}
    </ul>;

const Child = ({ content }) => <li>
      <span>#{content.id} {content.text}</span>
    </li>;

ReactDOM.render(<Root contents={contents} />, document.getElementById('root'));

Parentの定義にChildが含まれています。
つまりParentChildに依存しているということです。
この依存を解消したくなりました。

Step.2 Rootで構造を定義してみた

const Root = ({ contents }) => <Parent contents={contents}>
      {contents.map(x => <Child key={x.id} content={x}/>)}
    </Parent>;

const Parent = ({ children }) => <ul>
      {children}
    </ul>;

const Child = ({ content }) => <li>
      <span>#{content.id} {content.text}</span>
    </li>;

ReactDOM.render(<Root contents={contents} />, document.getElementById('root'));

ParentChildの構造をRootで定義するようにしました。
これでParentからChildへの依存を解消できました。

この時点で、ReactではParentChildのように独立したコンポーネントとRootのように構造化を司るコンポーネントに分けて設計した方がよいコードになりそうだなと思うようになりました。

ただ、これだとChildli要素を持っていて親(ul要素を持つコンポーネント)の存在を暗に示しています。
また、Parentli要素を持っていないので子の存在を暗に示しています。
ChildからParentli要素を移動させると独立性をもっと高められるような気がしました。

Step.3 ParentChildの独立性を高めた

const Root = ({ contents }) => <Parent contents={contents}>
      {contents.map(x => <Child key={x.id} content={x}/>)}
    </Parent>;

const Parent = ({ children }) => <ul>
      {React.Children.map(children, x =>
          <li key={x.props.content.id}>{x}</li>
        )}
    </ul>;

const Child = ({ content }) => <span>#{content.id} {content.text}</span>;

ReactDOM.render(<Root contents={contents} />, document.getElementById('root'));

ChildからParentli要素を移動しました。
これで暗黙的な親子の気配を取り除けたのでそれぞれ独立性が高まったと思います。

しかし、Parentli要素にkeyに設定している値が子コンポーネントのprops.content.idとなっています。
これは子コンポーネントに渡されるpropsの詳細を知っていることになり、完全には依存を解消できていません。

Step.4 keyになり得る値を引き回すルールを決めた

const Root = ({ contents }) => <Parent contents={contents}>
      {contents.map(x => <Child key={x.id} relayedKey={x.id} content={x}/>)}
    </Parent>;

const Parent = ({ children }) => <ul>
      {React.Children.map(children, x =>
          <li key={x.props.relayedKey}>{x}</li>
        )}
    </ul>;

const Child = ({ content }) => <span>#{content.id} {content.text}</span>;

ReactDOM.render(<Root contents={contents} />, document.getElementById('root'));

配列から作られる繰り返しのコンポーネントにはrelayedKeyという名前でkeyと同じ値を設定するようにルールを決めました。
childrenmapするとき、mapの前後でコンポーネントは1対1になるのでrelayedKeykeyとして使えます。

これでParentも独立性が高まりました。

もっとよい方法はないか?

Step.4で示したrelayedKeyルールの導入が今私が考えられる最善の方法です。
とはいえ単に名前を取り決めるというルールで縛っているだけなので納得はしていません。

もっとよい方法はありませんかね?|・`ω・)チラッ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away