TL;DR
React.cloneElementを使ってchildrenを複製することで任意のpropsを追加できます。
const newChildren = React.cloneElement(children, additionalProps)
// React.cloneElementの第一引数にchildren, 第二引数に追加したいpropsを渡す
React.cloneElementとは
React.cloneElement()
はElementを複製するメソッドです。
引数に以下のような3つの値を受け取ります。
React.cloneElement(element, props, [...children])
// element: ReactのElement
// props: 複製するElementにわたすprops, elementがもともと持っていたpropsとマージされる
// children: 複製するElementにわたすchildren
childrenにpropsを追加する
React.cloneElement()
を使ってchildrenを複製しましょう。
このときReact.cloneElement()
の第二引数に追加したいpropsを渡すことで、childrenにpropsを追加することができます。
const Example = ({children}) => {
const additionalProps = {hoge: 'hoge', ...}
const newChildren = React.cloneElement(children, additionalProps)
return (
<div>{newChildren}</div>
)
}
一点注意すべきこととして、React.cloneElementの第一引数にはElementしか取れないことがあります。
childrenはElement以外にstringやElementの配列の場合もあります。
そのため上記のExampleのchildrenにElement以外の値を渡した場合、エラーになります。
TypeScriptを使っている場合は以下のようにchildrenに型をつけるとよいでしょう。
interface Props {
children: React.ReactElement;
}
const Example: React.FC<Props> = ({children}) => ( ... )
利用例
上記のようなchildrenに2つのElementをを受け取って、2カラムで表示するレイアウトコンポーネントを考えます。
childrenに2つのElementを受け取って、それぞれに対してstyleをpropsから渡せばよいです。
interface Props {
children: [React.ReactElement, React.ReactElement];
}
const TwoColumnLayout: React.FC<Props> = ({ children }) => {
const left = React.cloneElement(children[0], {
style: { ...children[0].props.style, flex: "1", marginRight: "10px" }
});
const right = React.cloneElement(children[1], {
style: { ...children[1].props.style, flex: "1" }
});
return (
<div style={{ display: "flex" }}>
{left}
{right}
</div>
);
};
const App: React.FC = () => (
<TwoColumnLayout>
<div style={{ background: "blue" }}>left</div>
<div style={{ background: "red" }}>right</div>
</TwoColumnLayout>
);
React.cloneElementを使わないやり方
React.cloneElementを使わずに実現しようとすると以下のようになります。
interface Props {
children: [React.ReactElement, React.ReactElement];
}
const TwoColumnLayout: React.FC<Props> = ({ children }) => {
return (
<div style={{ display: "flex" }}>
<div style={{ flex: "1", marginRight: "10px" }}>{children[0]}</div>
<div style={{ flex: "1" }}>{children[1]}</div>
</div>
);
};
React.cloneElementを使った場合と比較すると、こちらのほうがコードとしては簡潔です。
しかしflex:1
を与えるためにchildrenをラップするdivタグが必要になります。
余計なdivタグが増えてしまうことを許容できるのであれば、このやり方でもよいと思います。
私は余分なdivタグが増えるのは好きではないので、React.cloneElementを使ったやり方でいつも書いています。