概要
配列をもとにしてこんなビューを作りたいときに試行錯誤してコンポーネントの独立性を高められたけれどkey
の設定に悩んでいる、という話です。
<ul>
<li><span>#1 foo</span></li>
<li><span>#2 bar</span></li>
<li><span>#3 baz</span></li>
</ul>
どういう経緯でkey
の悩みに至ったか段階的に書いていきます。
以降に出てくるコード例の前提
出てくるコンポーネントはRoot
、Parent
、Child
の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
が含まれています。
つまりParent
はChild
に依存しているということです。
この依存を解消したくなりました。
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'));
Parent
とChild
の構造をRoot
で定義するようにしました。
これでParent
からChild
への依存を解消できました。
この時点で、ReactではParent
やChild
のように独立したコンポーネントとRoot
のように構造化を司るコンポーネントに分けて設計した方がよいコードになりそうだなと思うようになりました。
ただ、これだとChild
がli
要素を持っていて親(ul
要素を持つコンポーネント)の存在を暗に示しています。
また、Parent
もli
要素を持っていないので子の存在を暗に示しています。
Child
からParent
へli
要素を移動させると独立性をもっと高められるような気がしました。
Step.3 Parent
とChild
の独立性を高めた
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
からParent
へli
要素を移動しました。
これで暗黙的な親子の気配を取り除けたのでそれぞれ独立性が高まったと思います。
しかし、Parent
でli
要素に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
と同じ値を設定するようにルールを決めました。
children
をmap
するとき、map
の前後でコンポーネントは1対1になるのでrelayedKey
がkey
として使えます。
これでParent
も独立性が高まりました。
もっとよい方法はないか?
Step.4で示したrelayedKey
ルールの導入が今私が考えられる最善の方法です。
とはいえ単に名前を取り決めるというルールで縛っているだけなので納得はしていません。
もっとよい方法はありませんかね?|・`ω・)チラッ