JavaScript
reactjs
React

特定のReactコンポーネントの子要素に親要素からpropsを暗黙的に渡す方法

More than 1 year has passed since last update.

複数のコンポーネントをワンセットで使いたい/使わせたい場面はしばしばあります。例えばTable関連のコンポーネントは、それぞれ別々に定義したほうが使い勝手は良いですが、tableタグの子要素や孫要素で発生したイベントなどはまとめてtableタグのpropsに渡すハンドラで処理したくなります。ここでは話を単純にするために、2階層の以下のような場面を想定します。


example1

export default class App extends React.Component {

render() {
return (
<TableBody
onRowSelection={rows => {console.log(rows)}}
>
{this.props.users.map((user, i) => {
return (
<TableRow key={user.id}>
<TableRowColumn>{user.id}</TableRowColumn>
<TableRowColumn>{user.name}</TableRowColumn>
<TableRowColumn>{user.email}</TableRowColumn>
</TableRow>
);
})}
</TableBody>
);
}
}

TableBodyはtbodyタグを、TableRowはtrタグを、TableRowColumnはtdタグをそれぞれラップしています。TableRowで発生したなんらかのイベントをTableBodyのpropsに渡したonRowSelectionハンドラで処理しているとします。

具体的には以下のようにします。


example2

class TableBody extends React.Component {

static displayName = "TableBody";

public render() {
return (
<tbody>
{React.Children.map(this.props.children, (child, i) => {
if (
!React.isValidElement(child) ||
child.type.displayName !== TableRow.displayName
) {
console.warn(`Children of the TableBody component must be TableRow`);
return;
}
return React.cloneElement(
child as React.ReactElement,
{
rowNumber: i,
onRowSelection: (isChecked, rowNumber) => {
this.props.onRowSelection(isChecked, rowNumber);
},
}
);
})}
</tbody>
);
}
}

class TableRow extends React.Component {
static displayName = "TableRow";

render() {
return (
<tr>
<td>
<input type="checkbox" onChange={e => {
this.props.onRowSelection(e.target.checked, this.props.rowNumber);
}} />
</td>
{this.props.children}
</tr>
);
}
}


ReactのChildren.mapメソッドでchildrenをループで検査して、propsを渡したいコンポーネントならcloneElementでpropsを追加します。Reactコンポーネントにstaticメンバを追加すると、親要素からchild.type.displayNameのようにアクセスできるので、それでコンポーネントを特定します。用意したコンポーネント以外を子要素に入れられたくない場合は、console.warnなどで警告を出してあげると親切です。