Children
Children
は、props である children
から受け取った JSX を操作、変換するために用います。
const mappedChildren = Children.map(children, child =>
<div className="Row">
{child}
</div>
);
リファレンス
Children.count(children)
Children.count(children)
を呼び出して、children
データ構造内の子の数をカウントします。
import { Children } from 'react';
function RowList({ children }) {
return (
<>
<h1>Total rows: {Children.count(children)}</h1>
...
</>
);
}
引数
children
: コンポーネントが props として受け取るchildren
の値。
返り値
当該 children
内部にあるノードの数。
注意点
- 空のノード(
null
、undefined
、およびブーリアン値)、文字列、数値、および React 要素が、個々のノードとしてカウントされます。配列自体は個別のノードとしてカウントされませんが、その子はカウントされます。React 要素より深い走査は行われません。要素がその場でレンダーされるわけではないため、子の走査も起きません。フラグメントも走査されません。
Children.forEach(children, fn, thisArg?)
Children.forEach(children, fn, thisArg?)
を呼び出して、children
データ構造内のそれぞれの子に対して何らかのコードを実行することができます。
import { Children } from 'react';
function SeparatorList({ children }) {
const result = [];
Children.forEach(children, (child, index) => {
result.push(child);
result.push(<hr key={index} />);
});
// ...
引数
children
: コンポーネントが props として受け取るchildren
の値。fn
: それぞれの子に対して実行したい関数。配列のforEach
メソッド のコールバックに似ています。子を第 1 引数、そのインデックスを第 2 引数として呼び出されます。インデックスは0
から始まり、呼び出しごとに増加します。- 省略可能
thisArg
:fn
関数が呼び出される際のthis
の値。省略された場合はundefined
になります。
返り値
Children.forEach
は undefined
を返します。
注意点
- 空のノード(
null
、undefined
、およびブーリアン値)、文字列、数値、および React 要素が、個々の子ノードとして扱われます。配列自体は個別のノードとして扱われませんが、その中身は子ノードとして扱われます。React 要素より深い走査は行われません。要素がその場でレンダーされるわけではないため、子の走査も起きません。フラグメントも走査されません。
Children.map(children, fn, thisArg?)
Children.map(children, fn, thisArg?)
を呼び出して、children
データ構造内のそれぞれの子をマップ(変換)します。
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
引数
children
: コンポーネントが props として受け取るchildren
の値。fn
:配列のmap
メソッド のコールバックに似たマッピング関数。子を第 1 引数、そのインデックスを第 2 引数として呼び出されます。インデックスは0
から始まり、呼び出しごとに増加します。この関数からは React ノードを返す必要があります。つまり空のノード(null
、undefined
、またはブーリアン値)、文字列、数値、React 要素、または他の React ノードの配列です。- 省略可能
thisArg
:fn
関数が呼び出される際のthis
の値。省略された場合はundefined
になります。
返り値
children
が null
または undefined
の場合、同じ値を返します。
それ以外の場合、fn
関数から返されたノードで構成されるフラットな配列を返します。返された配列には null
と undefined
を除くすべてのノードが含まれます。
注意点
-
空のノード(
null
、undefined
、およびブーリアン値)、文字列、数値、および React 要素が、個々の子ノードとして扱われます。配列自体は個別のノードとして扱われませんが、その中身は子ノードとして扱われます。React 要素より深い走査は行われません。要素がその場でレンダーされるわけではないため、子の走査も起きません。フラグメントも走査されません。 -
fn
から key 付きで要素ないし要素の配列を返す場合、返された要素の key は、children
の対応する元の項目のキーと自動的に結合されます。fn
から複数の要素を配列で返す場合、それらの key はその内部でローカルに一意であれば十分です。
Children.only(children)
Children.only(children)
を呼び出すことで children
が単一の React 要素を表していることを確認します。
function Box({ children }) {
const element = Children.only(children);
// ...
引数
children
: コンポーネントが props として受け取るchildren
の値。
返り値
children
が有効な要素である場合、その要素を返します。
それ以外の場合、エラーをスローします。
注意点
- このメソッドは、
children
に配列(Children.map
の返り値など)を渡すと常にエラーをスローします。つまり、children
が単一要素の配列などではなく、単一の React 要素そのものであることを強制します。
Children.toArray(children)
Children.toArray(children)
を呼び出して、children
データ構造から配列を作成します。
import { Children } from 'react';
export default function ReversedList({ children }) {
const result = Children.toArray(children);
result.reverse();
// ...
引数
children
: コンポーネントが props として受け取るchildren
の値。
返り値
children
内の内容のフラットな配列を返します。
注意点
- 空ノード(
null
、undefined
、およびブーリアン値)は返される配列からは省かれます。返される要素の key は、元の要素の key と、そのネストレベルや位置から計算されます。これにより、配列のフラット化により挙動が変化しないことが保証されます。
使用法
子の変換
コンポーネントが children
プロパティとして受け取った子の JSX を変換するために、Children.map
を呼び出します。
import { Children } from 'react';
function RowList({ children }) {
return (
<div className="RowList">
{Children.map(children, child =>
<div className="Row">
{child}
</div>
)}
</div>
);
}
上記の例では、RowList
は受け取ったすべての子を <div className="Row">
というコンテナにラップします。例えば、親コンポーネントが 3 つの <p>
タグを props 経由で children
として RowList
に渡すとしましょう。
<RowList>
<p>This is the first item.</p>
<p>This is the second item.</p>
<p>This is the third item.</p>
</RowList>
上記の RowList
の実装により、最終的にレンダーされる結果は次のようになります。
<div className="RowList">
<div className="Row">
<p>This is the first item.</p>
</div>
<div className="Row">
<p>This is the second item.</p>
</div>
<div className="Row">
<p>This is the third item.</p>
</div>
</div>
Children.map
は map()
を使って配列を変換する のと似ています。違いは、children
のデータ構造を非公開 (opaque) のものと見なすべきであることです。これは、children
が実際に配列である場合があるとしても、それを配列あるいは他の特定のデータ型であると仮定してはならないという意味です。これが、子の変換が必要な場合には Children.map
を使用すべき理由です。
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
さらに深く知る
React では props としての children
は非公開のデータ構造だと見なされます。つまりその具体的な構造に依存してはいけないという意味です。子を変換したり、フィルタリングしたり、数えたりするためには、Children
のメソッドを使用すべきです。
実際には、children
データ構造は内部的にはしばしば配列として表現されます。しかし、子が 1 つだけの場合、React は不必要なメモリオーバーヘッドを避けるため、余分な配列を作成しません。children
の中身を直接覗くのではなく、Children
のメソッドを使用する限り、React がデータ構造の実装方法を変更してもあなたのコードは壊れずに済みます。
children
が配列である場合でも、Children.map
には便利な特別な振る舞いがあります。例えば、Children.map
は、返された要素の key と、渡された children
にある key を組み合わせます。これにより、上記の例のようにラップされても元の子 JSX がキーを「失う」ことはありません。
子のそれぞれに対してコードを実行する
Children.forEach
を呼び出すことで、children
データ構造の子のそれぞれに対して反復処理を行えます。これは値を返さない、配列の forEach
メソッドに似たものです。独自の配列を構築するなどのカスタムロジックを実行するために使用できます。
import { Children } from 'react'; export default function SeparatorList({ children }) { const result = []; Children.forEach(children, (child, index) => { result.push(child); result.push(<hr key={index} />); }); result.pop(); // Remove the last separator return result; }
import { Children } from 'react'; export default function RowList({ children }) { return ( <div className="RowList"> <h1 className="RowListHeader"> Total rows: {Children.count(children)} </h1> {Children.map(children, child => <div className="Row"> {child} </div> )} </div> ); }
子を配列に変換する
Children.toArray(children)
を呼び出して、children
データ構造を通常の JavaScript 配列に変換します。これにより、filter
、sort
、reverse
などの組み込み配列メソッドを使って配列を操作できます。
import { Children } from 'react'; export default function ReversedList({ children }) { const result = Children.toArray(children); result.reverse(); return result; }
代替手段
複数のコンポーネントを公開する
Children
のメソッドを使って children を操作することで、しばしばコードが壊れやすくなります。JSX でコンポーネントに children を渡す場合、通常はコンポーネントにより個々の子が操作されたり変換されたりすることを予想していないでしょう。
できる限り Children
メソッドの使用は避けてください。例えば、RowList
のすべての子を <div className="Row">
でラップしたい場合、Row
コンポーネントをエクスポートし、このように各行を手動でラップします。
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>This is the first item.</p> </Row> <Row> <p>This is the second item.</p> </Row> <Row> <p>This is the third item.</p> </Row> </RowList> ); }
Children.map
を使用する場合とは異なり、このアプローチではすべての子を自動的にラップしてくれません。しかし先ほどの Children.map
を使用した例と比較しても、このアプローチには、さらに多くのコンポーネントを抽出しても機能するという利点があります。例えば、自前の MoreRows
コンポーネントを抽出しても機能します。
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList> <Row> <p>This is the first item.</p> </Row> <MoreRows /> </RowList> ); } function MoreRows() { return ( <> <Row> <p>This is the second item.</p> </Row> <Row> <p>This is the third item.</p> </Row> </> ); }
これは Children.map
では機能しません。なぜなら、<MoreRows />
が単一の子(つまり単一の行)のように「見える」からです。
配列を props として受け入れる
明示的に配列を props として渡すこともできます。例えば、以下の RowList
は rows
という配列を props として受け取ります。
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rows={[ { id: 'first', content: <p>This is the first item.</p> }, { id: 'second', content: <p>This is the second item.</p> }, { id: 'third', content: <p>This is the third item.</p> } ]} /> ); }
rows
は通常の JavaScript の配列なので、RowList
コンポーネントは map
のような組み込みの配列メソッドを使用できます。
このパターンは特に、子と一緒に構造化データとしてより多くの情報を渡したい場合に有用です。以下の例では、TabSwitcher
コンポーネントは props である tabs
経由でオブジェクトの配列を受け取ります。
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabs={[ { id: 'first', header: 'First', content: <p>This is the first item.</p> }, { id: 'second', header: 'Second', content: <p>This is the second item.</p> }, { id: 'third', header: 'Third', content: <p>This is the third item.</p> } ]} /> ); }
JSX として子を渡すのとは異なり、このアプローチでは header
のような追加のデータを各アイテムに関連付けることができます。tabs
を直接操作しており、それは配列なので、Children
メソッドは必要ありません。
レンダープロップを呼び出してレンダーをカスタマイズする
すべてのアイテムに対して JSX を生成しておく代わりに、JSX を返す関数を渡し、必要なときにその関数を呼び出してもらうこともできます。以下の例では、App
コンポーネントは renderContent
という関数を TabSwitcher
コンポーネントに渡しています。TabSwitcher
コンポーネントは選択中のタブのみに対して renderContent
を呼び出します。
import TabSwitcher from './TabSwitcher.js'; export default function App() { return ( <TabSwitcher tabIds={['first', 'second', 'third']} getHeader={tabId => { return tabId[0].toUpperCase() + tabId.slice(1); }} renderContent={tabId => { return <p>This is the {tabId} item.</p>; }} /> ); }
renderContent
のような props は、ユーザインターフェースの一部をどのようにレンダーするかを指定する props であるため、レンダープロップ (render prop) と呼ばれます。しかし、これについて特別なことは何もありません。たまたたま関数型であるというだけの通常の props に過ぎません。
レンダープロップは関数なので、情報を渡すことができます。例えば、以下の RowList
コンポーネントは、各行の id
と index
を renderRow
というレンダープロップに渡し、index
を使って偶数行をハイライトします。
import { RowList, Row } from './RowList.js'; export default function App() { return ( <RowList rowIds={['first', 'second', 'third']} renderRow={(id, index) => { return ( <Row isHighlighted={index % 2 === 0}> <p>This is the {id} item.</p> </Row> ); }} /> ); }
このような方法でも、親コンポーネントと子コンポーネントが、子の操作を行わずに協調動作できるということです。
トラブルシューティング
カスタムコンポーネントを渡しているが、Children
メソッドがそのレンダー結果を表示しない
RowList
に以下のように 2 つの子を渡すとします。
<RowList>
<p>First item</p>
<MoreRows />
</RowList>
RowList
の中で Children.count(children)
を行うと、結果は 2
になります。MoreRows
が 10 の異なるアイテムをレンダーする場合でも、null
を返す場合でも、Children.count(children)
はやはり 2
になります。RowList
の視点からは受け取った JSX のみが「見えて」いるからです。MoreRows
コンポーネントの中身は「見えて」いません。
この制限はコンポーネントの抽出を困難にします。これが Children
を使用するのではなく、代替手段を使用すべき理由です。