リファレンス

cloneElement(element, props, ...children)

cloneElement を呼び出して、element を基に、異なる propschildren を持った React 要素を作成します。

引数

  • element: element 引数は有効な React 要素でなければなりません。例えば、<Something /> のような JSX ノード、 の呼び出し結果、または別の cloneElement の呼び出し結果などです。

  • props: props 引数はオブジェクトか null でなければなりません。null を渡すと、クローンされた要素は元の element.props をすべて保持します。それ以外の場合、props オブジェクト内のすべての項目について、返される要素では element.props の値よりも props からの値が「優先」されます。残りの props は元の element.props から埋められます。props.keyprops.ref を渡した場合、それらは元のものを置き換えます。

  • 省略可能 ...children: ゼロ個以上の子ノード。あらゆる React ノード、つまり React 要素、文字列、数値、、空ノード(nullundefinedtruefalse)、React ノードの配列になります。...children 引数を渡さない場合、元の element.props.children が保持されます。

返り値

cloneElement は以下のプロパティを持つ React 要素オブジェクトを返します。

  • type: element.type と同じ。
  • props: element.props に、渡された上書き用の props を浅くマージした結果。
  • ref: 元の element.ref。ただし、props.ref によって上書きされた場合は除く。
  • key: 元の element.key。ただし、props.key によって上書きされた場合は除く。

通常、この要素をコンポーネントから返すか、他の要素の子として用います。要素のプロパティを読み取ることは可能ですが、作成後は要素の構造を非公開 (opaque) として扱い、レンダーのみ行うようにするべきです。

注意点

  • 要素をクローンしても元の要素は変更されません

  • 複数の子の内容がすべて静的に分かっている場合cloneElement には子を cloneElement(element, null, child1, child2, child3) のように複数の引数として渡してください。子が動的な場合は、配列全体を第 3 引数として cloneElement(element, null, listItems) のように渡してください。これにより、React は動的なリストに key が欠けている場合にようになります。静的なリストでは並び替えは決して発生しないため、key は必要ありません。

  • cloneElement を使うとデータフローの追跡が難しくなるため、代わりにを試してみてください。


使用法

要素の props を上書きする

React 要素 の props を上書きするには、それを cloneElement に渡し、上書きしたい props を指定します。

この場合、結果となるクローンされた要素<Row title="Cabbage" isHighlighted={true} /> になります。

例を使って、これが役立つ場面を見てみましょう

選択可能な行のリストと、選択されている行を変更する “Next” ボタンをレンダーする List コンポーネントを想像してみてください。List コンポーネントは、選択された Row を異なる方法でレンダーする必要があるため、受け取ったすべての <Row> をクローンし、isHighlighted: true または isHighlighted: false を追加の props として指定します。

例えば List が受け取る元の JSX が以下のようなものである場合を考えます。

子要素をクローンすることで、List は内部のすべての Row に追加情報を渡すことができます。結果は以下のようになります。

“Next” を押すと List の state が更新され、異なる行がハイライトされることに着目してください。

おさらいすると、List は受け取った <Row /> 要素をクローンし、それらに追加の props を付加したということです。


代替手段

レンダープロップを用いてデータを渡す

cloneElement を使用する代わりに、renderItem のようなレンダープロップ (render prop) を受け取るようにすることを検討してみてください。以下の例では、ListrenderItem を props として受け取ります。List は各アイテムに対して renderItem を呼び出し、isHighlighted を引数として渡します。

renderItem のようなものは「レンダープロップ」と呼ばれます。何かをレンダーする方法を指定するための props だからです。例えば、与えられた isHighlighted の値で <Row> をレンダーする renderItem の実装を渡すことができます。

最終的な結果は cloneElement と同じです。

しかし、isHighlighted 値がどこから来ているかを明確に追跡することができます。

このパターンはより明示的であるため、cloneElement よりも推奨されます。


コンテクストでデータを渡す

cloneElement の別の代替手段としてことが可能です。

例として、createContext を呼び出して HighlightContext を定義しましょう。

List コンポーネントは、レンダーするすべてのアイテムを HighlightContext プロバイダでラップします。

このアプローチでは、Row は props で isHighlighted を受け取る必要が一切ありません。代わりにコンテクストから読み取ります。

これにより、呼び出し元のコンポーネントは <Row>isHighlighted を渡すことについて知る必要も、気にする必要もなくなります。

代わりに、ListRow はコンテクストを通じ、ハイライトのロジックに関して協調して動作します。


ロジックをカスタムフックに抽出する

試すべき別のアプローチは、「非視覚的」なロジックを自前のフックに抽出し、フックから返される情報を使用して何をレンダーするかを決定することです。例えば次のような useList カスタムフックを書くことができます。

これを以下のように使用できます。

データフローは明示的ですが、state は任意のコンポーネントから使用できる useList カスタムフック内にあります。

このアプローチは、特にこのロジックを異なるコンポーネント間で再利用したい場合に有用です。