cloneElement()
| 対応: | React 16.8(2019) |
|---|
『React』の cloneElement() は、既存の React 要素を複製して新しい要素を生成する関数です。複製時に props を追加・上書きでき、子要素も差し替えられます。主に親コンポーネントが子コンポーネントへ追加の props を注入するパターンで使われます。
構文
// element: 複製元の React 要素 // props: 上書き・追加したい props オブジェクト(省略可) // ...children: 新しい子要素(省略した場合は元の children を引き継ぐ) React.cloneElement(element, props, ...children)
引数一覧
| 引数 | 概要 |
|---|---|
| element | 複製元となる React 要素を指定します。有効な React 要素(JSX や React.createElement() の戻り値)でなければなりません。 |
| props | 元の要素の props にマージ(追加・上書き)したいオブジェクトを指定します。省略した場合は元の props がそのまま引き継がれます。 |
| ...children | 新しい子要素を指定します。指定した場合は元の子要素が置き換えられます。省略した場合は元の children が引き継がれます。 |
サンプルコード
ボタングループコンポーネントが、渡された子ボタンに対して共通の size props と onClick ハンドラを自動注入する例です。
// Button コンポーネント
// size と onClick は ButtonGroup から注入されます
function Button({ label, size, onClick }) {
// size に応じてスタイルを切り替えます
const style = {
padding: size === 'large' ? '12px 24px' : '6px 12px',
fontSize: size === 'large' ? '18px' : '14px',
margin: '4px',
cursor: 'pointer',
};
return (
<button style={style} onClick={onClick}>
{label}
</button>
);
}
// ButtonGroup コンポーネント
// children に渡されたボタン要素を cloneElement で複製し、
// size と onClick を追加注入してから描画します
function ButtonGroup({ size, onSelect, children }) {
return (
<div>
{React.Children.map(children, function(child) {
// cloneElement で既存の要素を複製しながら props を上書きします
// 元の props(label など)はそのまま引き継がれます
return React.cloneElement(child, {
size: size,
onClick: function() {
// child.props.label を使って選択されたボタンを通知します
onSelect(child.props.label);
},
});
})}
</div>
);
}
// App コンポーネント(ルートコンポーネント)
function App() {
const [selected, setSelected] = React.useState(null);
return (
<div>
<p>選択中: {selected ?? '(未選択)'}</p>
{/* ButtonGroup に size と onSelect を渡し、
各 Button への注入は ButtonGroup 内部で行います */}
<ButtonGroup size="large" onSelect={setSelected}>
<Button label="りんご" />
<Button label="みかん" />
<Button label="ぶどう" />
</ButtonGroup>
</div>
);
}
export default App;
概要
cloneElement() は、受け取った React 要素を変更せずに複製し、新しい props や子要素を付加した別の要素を返します。元の要素は変更されず、key と ref も引き継がれます(props で明示的に上書きした場合を除きます)。
典型的なユースケースは、親コンポーネントが children を走査しながら各子要素へ追加情報を注入するパターンです。上記サンプルのように React.Children.map() と組み合わせることで、子要素の型を意識せずに一括して props を渡せます。
ただし、cloneElement() を多用すると props の流れが暗黙的になり、コードの追跡が難しくなります。『React』公式では、代替手段として useContext や render props パターンの採用を推奨しています。シンプルな props の受け渡しには props を直接使う方が読みやすい場合が多いです。
関連ページ: children / props / useContext
よくあるミス
children に React 要素以外が混在している場合のエラー
children に文字列や数値が混在している場合、cloneElement はそれらに対して失敗します。React.Children.map と React.isValidElement を使って React 要素のみにフィルタリングします。
clone_ng.jsx
function ButtonGroup({ size, children }) {
return (
<div>
{React.Children.map(children, function(child) {
// child が文字列や null のとき cloneElement はエラーになります
return React.cloneElement(child, { size: size });
})}
</div>
);
}
function App() {
return (
<ButtonGroup size="large">
<button>送信</button>
テキストが混入
</ButtonGroup>
);
}
修正後:
clone_ok.jsx
function ButtonGroup({ size, children }) {
return (
<div>
{React.Children.map(children, function(child) {
// React 要素のみを cloneElement の対象にします
if (!React.isValidElement(child)) return child;
return React.cloneElement(child, { size: size });
})}
</div>
);
}
function App() {
return (
<ButtonGroup size="large">
<button>送信</button>
テキストが混入
</ButtonGroup>
);
}
key プロパティの上書き
cloneElement で key を指定しないと、元の要素の key が引き継がれます。リスト内で複数の要素を複製する際、元の key が重複していないか確認し、必要に応じて一意な key を明示的に指定します。
key_ng.jsx
function TabGroup({ children }) {
// key を指定しないため、元の key がそのまま引き継がれます
// 同じ key が複数箇所に存在すると React が警告を出します
const tabs = React.Children.map(children, function(child) {
return React.cloneElement(child, { active: true });
});
const panels = React.Children.map(children, function(child) {
return React.cloneElement(child, { active: false });
});
return <div>{tabs}{panels}</div>;
}
修正後:
key_ok.jsx
function TabGroup({ children }) {
const tabs = React.Children.map(children, function(child, i) {
// プレフィックスを付けて一意な key を指定します
return React.cloneElement(child, { active: true, key: 'tab-' + i });
});
const panels = React.Children.map(children, function(child, i) {
return React.cloneElement(child, { active: false, key: 'panel-' + i });
});
return <div>{tabs}{panels}</div>;
}
既存 props の意図しない上書き
cloneElement の第2引数で渡した props は既存の props を上書きします。元の props を保持したまま追加したい場合は、スプレッドで既存の props を展開してから新しい props を追加します。
props_override_ng.jsx
function Wrapper({ children }) {
return (
<div>
{React.Children.map(children, function(child) {
// onClick だけ渡すと、子が持っていた既存の onClick が上書きされます
return React.cloneElement(child, {
onClick: function() { console.log('wrapper clicked'); },
});
})}
</div>
);
}
修正後:
props_override_ok.jsx
function Wrapper({ children }) {
return (
<div>
{React.Children.map(children, function(child) {
var originalOnClick = child.props.onClick;
return React.cloneElement(child, {
// 既存の props を展開してから追加の props を上書きします
...child.props,
onClick: function(e) {
// 既存の onClick も呼び出してから wrapper の処理を行います
if (originalOnClick) originalOnClick(e);
console.log('wrapper clicked');
},
});
})}
</div>
);
}
記事の間違いや著作権の侵害等ございましたらお手数ですがこちらまでご連絡頂ければ幸いです。