React Slotが便利✨
Radix UIのSlotコンポーネントが地味ですが、大変便利です。
概要
Radix UIにはユーティリティのコンポーネントであるSlotがあります。
実装は実にシンプルです。
提供する機能もシンプルですが非常に強力です。
基本
直下の子コンポーネントにpropsを渡すことができます。
子コンポーネントにpropsを渡す方法としてはcloneElementが有名(検索がよくヒットする)ですが、ほとんどのケースにおいてSlotに置き換えたほうが実装が楽になります。
実装がシンプルになる
単純に記述量が減ります。
これが
const Component: FC<Props> = ({ children }) => {
const newChildren = cloneElement(children as ReactElement, { color: 'red' });
return <div>{newChildren}</div>;
};
こうなります。
const Component: FC<Props> = ({ children }) => {
return (
<div>
<Slot color="red">{children}</Slot>
</div>
);
};
ReactNode型で定義することの多いchildrenをキャストする手間も省けるので楽です。
mergePropsがありがたい
内部的に、子コンポーネントに元々あるpropsとSlotから渡されるpropsの衝突を抑止してくれます。
mergeProps関数がこの役割を担います。
-
onXXXXハンドラは子コンポーネントのハンドラから実行 - スタイリング要素は文字列として連結(Slot優先)
するように手を加えていることがわかります。
classNameのマージが発生するような箇所なんかは特に楽です。何も考えずに対象コンポ―ネントをSlotで囲って、そこにclassNameを含むpropsを流し込むだけなので。
Slottable
Slottableを併用したときのSlotの挙動は少々特殊です。
例えば、次のようにComponentを実装したとき、
const Component = () => (
<Slot {...slotProps}>
<Child1 />
<Slottable>
<Parent>
<Child2 />
</Parent>
</Slottable>
<Child3 />
</Slot>
);
Componentは、こうなります。
const Component = () => (
<Parent {...slotProps}>
<Child1 />
<Child2 />
<Child3 />
</Parent>
);
-
Slottable直下の子コンポーネント(Parent)をSlotの位置に巻き上げる - さらにその子コンポーネント(
Child2)をSlot配下のchildrenとマージする
これがSlot・Slottableの動作です。
何がうれしいのか
~別記事に譲りますが執筆中です。~
そのままですが、子コンポーネントXに任意のコンポーネントをXの子供として後から押し込みたい場合に使用できます(説明がわかりづらいので先ほどのコードで雰囲気をつかんでいただきたいです)。
Framer Motionの解説がなされているこちらの記事の「Shared layout animations and LayoutGroup」に登場するリッチなホバーアニメーションがあります。
これを極力再利用できるような形に落とし込みたかったので、Slot・Slottableを利用しました。
Framer MotionとSlotの併用なので、当初はこちらの記事で紹介するつもりでした。ただ、明らかに混乱を招くことが予想されたので、行き場所を見失っていました。結局、別記事に譲ることなくこちらで供養することにしました。
まとめ
Slot単体で非常に便利でしたが、Slottable込みの場合のSlotの動作が変更された(される?)ことで、より強力な機能を提供してくれるようになりました。
Slotの実装はシンプルなので、mergePropsの挙動やSlottable込みの挙動が気に入らない場合は、オリジナルのSlotを実装してもいいかもしれませんね。
Discussion