ReactのJSXでも子要素を定義できますが、これが結構面白いものでした。
自作エレメントにも子要素
ReactのJSXでは、(DOMを組み立てるものである以上当然ですが)<div>
の中に<a>
を書いて、そしてさらに文字列を書き込む、ということが可能です。
そして、これはHTML由来のエレメントだけでなく、自作のコンポーネントでも実現可能です。
function SomeWrapper({children}) {
return(
<div className="some-class">
{ children }
</div>
);
}
このように、子要素はchildren
というPropとして渡ってきます。
children
の中身が知りたくて
では、このchildren
には何がどのような形式で来るのでしょうか。JSXの変換先であるReact.createElement
のソースコードに当たってみました。挙動はchildren
の数によって違います。
- 0個…
children
にはprop
として渡したchildren
が(もしあれば)渡される - 1個…
children
には唯一のchildren
引数がそのまま渡される1 - 2個以上…
children
には引数で渡されたものが配列に詰め込まれる
公式な操作方法
現実問題として、今からchildren
の実装を変えてしまうということは互換性問題などを考えれば可能性は薄いのですが、公式にはchildren
のデータ形式は「非公開」ということになっています。そこで、children
を操作するためのReact.Children
というユーティリティ関数群があります(リファレンス)。
-
React.Children.map
…children
の各要素に対して関数を実行して、結果を配列で得る。 -
React.Children.forEach
…children
の各要素に対して関数を実行する。 -
React.Children.count
…children
の個数を返す -
React.Children.toArray
…children
を本物の配列に変換して返す -
React.Children.only
…children
が1つのJSXエレメントであることを保証する
なお、forEach
、map
、toArray
で処理する際に、キーは子要素間で一意となるようなものがReact側で振られます。また、子要素として<React.Fragment>
を渡した場合、1つものとして扱われます。
children
とReact.memo
今のところ、React.createElement
をキャッシュするような仕組みはないので、<br />
のようなシンプルなJSX要素であっても、2回の実行でオブジェクトとして一致することはありません。さらに、上述のように複数の子要素をもたせた場合、children
の配列を生成しますので、このインスタンスも一致しません。
そして、PureComponent
やReact.memo
はchildren
を特別扱いしませんので、(文字列1つだけ指定するような例外的なケースは別として)children
を指定するようなコンポーネントでは、メモ化は利かないということになってしまいます。
対策としては、
- 子要素を取るコンポーネントを使う側の場合、「コンポーネント+子要素」全体を1つのコンポーネントとして、それをメモ化する
- 子要素を取るコンポーネントを作る場合、メモ化を諦める
などが考えられます。そして、children
以外でも、表示する文字列をpropで取る場合に、「HTMLタグで装飾したい」とその場で書いたJSXを渡せば同じ事態となります。こちらについては、React.useMemo
、クラスのプロパティ、外側で変数に定義するなど、同じJSXインスタンスを使い回す形として対応が可能かと思います。
-
Context.Consumer
などで子要素代わりにコールバックを書くことがありますが、1個なので関数がそのままchildren
に入る形となっています。 ↩