cloneElement
リファレンス
cloneElement(element, props, ...children)
cloneElement
を呼び出して、element
を基に、異なる props
と children
を持った React 要素を作成します。
引数
element
:element
引数は有効な React 要素でなければなりません。例えば、<Something />
のような JSX ノード、 の呼び出し結果、または別のcloneElement
の呼び出し結果などです。props
:props
引数はオブジェクトかnull
でなければなりません。null
を渡すと、クローンされた要素は元のelement.props
をすべて保持します。それ以外の場合、props
オブジェクト内のすべての項目について、返される要素ではelement.props
の値よりもprops
からの値が「優先」されます。残りの props は元のelement.props
から埋められます。props.key
やprops.ref
を渡した場合、それらは元のものを置き換えます。省略可能
...children
: ゼロ個以上の子ノード。あらゆる React ノード、つまり React 要素、文字列、数値、、空ノード(null
、undefined
、true
、false
)、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) を受け取るようにすることを検討してみてください。以下の例では、List
は renderItem
を props として受け取ります。List
は各アイテムに対して renderItem
を呼び出し、isHighlighted
を引数として渡します。
renderItem
のようなものは「レンダープロップ」と呼ばれます。何かをレンダーする方法を指定するための props だからです。例えば、与えられた isHighlighted
の値で <Row>
をレンダーする renderItem
の実装を渡すことができます。
最終的な結果は cloneElement
と同じです。
しかし、isHighlighted
値がどこから来ているかを明確に追跡することができます。
このパターンはより明示的であるため、cloneElement
よりも推奨されます。
コンテクストでデータを渡す
cloneElement
の別の代替手段としてことが可能です。
例として、createContext
を呼び出して HighlightContext
を定義しましょう。
List
コンポーネントは、レンダーするすべてのアイテムを HighlightContext
プロバイダでラップします。
このアプローチでは、Row
は props で isHighlighted
を受け取る必要が一切ありません。代わりにコンテクストから読み取ります。
これにより、呼び出し元のコンポーネントは <Row>
に isHighlighted
を渡すことについて知る必要も、気にする必要もなくなります。
代わりに、List
と Row
はコンテクストを通じ、ハイライトのロジックに関して協調して動作します。
ロジックをカスタムフックに抽出する
試すべき別のアプローチは、「非視覚的」なロジックを自前のフックに抽出し、フックから返される情報を使用して何をレンダーするかを決定することです。例えば次のような useList
カスタムフックを書くことができます。
これを以下のように使用できます。
データフローは明示的ですが、state は任意のコンポーネントから使用できる useList
カスタムフック内にあります。
このアプローチは、特にこのロジックを異なるコンポーネント間で再利用したい場合に有用です。